diff --git a/.coveragerc b/.coveragerc
index 5fd43d5aec7e9de759531e5ef78e55ee2257c766..b0f85b14c0678c9f76c5bfd44bfcd97a7f3e188d 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -578,6 +578,7 @@ omit =
     homeassistant/components/sensor/fitbit.py
     homeassistant/components/sensor/fixer.py
     homeassistant/components/sensor/folder.py
+    homeassistant/components/sensor/foobot.py
     homeassistant/components/sensor/fritzbox_callmonitor.py
     homeassistant/components/sensor/fritzbox_netmonitor.py
     homeassistant/components/sensor/gearbest.py
diff --git a/homeassistant/components/sensor/foobot.py b/homeassistant/components/sensor/foobot.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f65a3358726712b6846f1197864c957c051b6d9
--- /dev/null
+++ b/homeassistant/components/sensor/foobot.py
@@ -0,0 +1,158 @@
+"""
+Support for the Foobot indoor air quality monitor.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/sensor.foobot/
+"""
+import asyncio
+import logging
+from datetime import timedelta
+
+import aiohttp
+import voluptuous as vol
+
+import homeassistant.helpers.config_validation as cv
+from homeassistant.exceptions import PlatformNotReady
+from homeassistant.const import (
+    ATTR_TIME, ATTR_TEMPERATURE, CONF_TOKEN, CONF_USERNAME, TEMP_CELSIUS)
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
+from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
+from homeassistant.helpers.entity import Entity
+from homeassistant.util import Throttle
+
+
+REQUIREMENTS = ['foobot_async==0.3.0']
+
+_LOGGER = logging.getLogger(__name__)
+
+ATTR_HUMIDITY = 'humidity'
+ATTR_PM2_5 = 'PM2.5'
+ATTR_CARBON_DIOXIDE = 'CO2'
+ATTR_VOLATILE_ORGANIC_COMPOUNDS = 'VOC'
+ATTR_FOOBOT_INDEX = 'index'
+
+SENSOR_TYPES = {'time': [ATTR_TIME, 's'],
+                'pm': [ATTR_PM2_5, 'µg/m3', 'mdi:cloud'],
+                'tmp': [ATTR_TEMPERATURE, TEMP_CELSIUS, 'mdi:thermometer'],
+                'hum': [ATTR_HUMIDITY, '%', 'mdi:water-percent'],
+                'co2': [ATTR_CARBON_DIOXIDE, 'ppm',
+                        'mdi:periodic-table-co2'],
+                'voc': [ATTR_VOLATILE_ORGANIC_COMPOUNDS, 'ppb',
+                        'mdi:cloud'],
+                'allpollu': [ATTR_FOOBOT_INDEX, '%', 'mdi:percent']}
+
+SCAN_INTERVAL = timedelta(minutes=10)
+PARALLEL_UPDATES = 1
+
+TIMEOUT = 10
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+    vol.Required(CONF_TOKEN): cv.string,
+    vol.Required(CONF_USERNAME): cv.string,
+})
+
+
+async def async_setup_platform(hass, config, async_add_devices,
+                               discovery_info=None):
+    """Set up the devices associated with the account."""
+    from foobot_async import FoobotClient
+
+    token = config.get(CONF_TOKEN)
+    username = config.get(CONF_USERNAME)
+
+    client = FoobotClient(token, username,
+                          async_get_clientsession(hass),
+                          timeout=TIMEOUT)
+    dev = []
+    try:
+        devices = await client.get_devices()
+        _LOGGER.debug("The following devices were found: %s", devices)
+        for device in devices:
+            foobot_data = FoobotData(client, device['uuid'])
+            for sensor_type in SENSOR_TYPES:
+                if sensor_type == 'time':
+                    continue
+                foobot_sensor = FoobotSensor(foobot_data, device, sensor_type)
+                dev.append(foobot_sensor)
+    except (aiohttp.client_exceptions.ClientConnectorError,
+            asyncio.TimeoutError, FoobotClient.TooManyRequests,
+            FoobotClient.InternalError):
+        _LOGGER.exception('Failed to connect to foobot servers.')
+        raise PlatformNotReady
+    except FoobotClient.ClientError:
+        _LOGGER.error('Failed to fetch data from foobot servers.')
+        return
+    async_add_devices(dev, True)
+
+
+class FoobotSensor(Entity):
+    """Implementation of a Foobot sensor."""
+
+    def __init__(self, data, device, sensor_type):
+        """Initialize the sensor."""
+        self._uuid = device['uuid']
+        self.foobot_data = data
+        self._name = 'Foobot {} {}'.format(device['name'],
+                                           SENSOR_TYPES[sensor_type][0])
+        self.type = sensor_type
+        self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
+
+    @property
+    def name(self):
+        """Return the name of the sensor."""
+        return self._name
+
+    @property
+    def icon(self):
+        """Icon to use in the frontend."""
+        return SENSOR_TYPES[self.type][2]
+
+    @property
+    def state(self):
+        """Return the state of the device."""
+        try:
+            data = self.foobot_data.data[self.type]
+        except(KeyError, TypeError):
+            data = None
+        return data
+
+    @property
+    def unique_id(self):
+        """Return the unique id of this entity."""
+        return "{}_{}".format(self._uuid, self.type)
+
+    @property
+    def unit_of_measurement(self):
+        """Return the unit of measurement of this entity."""
+        return self._unit_of_measurement
+
+    async def async_update(self):
+        """Get the latest data."""
+        await self.foobot_data.async_update()
+
+
+class FoobotData(Entity):
+    """Get data from Foobot API."""
+
+    def __init__(self, client, uuid):
+        """Initialize the data object."""
+        self._client = client
+        self._uuid = uuid
+        self.data = {}
+
+    @Throttle(SCAN_INTERVAL)
+    async def async_update(self):
+        """Get the data from Foobot API."""
+        interval = SCAN_INTERVAL.total_seconds()
+        try:
+            response = await self._client.get_last_data(self._uuid,
+                                                        interval,
+                                                        interval + 1)
+        except (aiohttp.client_exceptions.ClientConnectorError,
+                asyncio.TimeoutError, self._client.TooManyRequests,
+                self._client.InternalError):
+            _LOGGER.debug("Couldn't fetch data")
+            return False
+        _LOGGER.debug("The data response is: %s", response)
+        self.data = {k: round(v, 1) for k, v in response[0].items()}
+        return True
diff --git a/requirements_all.txt b/requirements_all.txt
index 608618eb166c3672987dd36ff8ec5704eeec1c84..f2919eb9bc31598c9211b13749e146120ca3ba5f 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -287,6 +287,9 @@ fixerio==0.1.1
 # homeassistant.components.light.flux_led
 flux_led==0.21
 
+# homeassistant.components.sensor.foobot
+foobot_async==0.3.0
+
 # homeassistant.components.notify.free_mobile
 freesms==0.1.2
 
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 9def3a7b301b048fecc3d5349e97bdd646f36138..69b56eabc5e9704d136b80de28157a6907d3b140 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -62,6 +62,9 @@ evohomeclient==0.2.5
 # homeassistant.components.sensor.geo_rss_events
 feedparser==5.2.1
 
+# homeassistant.components.sensor.foobot
+foobot_async==0.3.0
+
 # homeassistant.components.tts.google
 gTTS-token==1.1.1
 
diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py
index a7704088e265196c8731ce321445f0e56d38f710..d8fc7b1ed604168c15ab8aa1b662c7b98729f5ef 100755
--- a/script/gen_requirements_all.py
+++ b/script/gen_requirements_all.py
@@ -47,6 +47,7 @@ TEST_REQUIREMENTS = (
     'ephem',
     'evohomeclient',
     'feedparser',
+    'foobot_async',
     'gTTS-token',
     'HAP-python',
     'ha-ffmpeg',
diff --git a/tests/components/sensor/test_foobot.py b/tests/components/sensor/test_foobot.py
new file mode 100644
index 0000000000000000000000000000000000000000..322f2b3f2a893aa2883f5541da8bcad07fba3ebf
--- /dev/null
+++ b/tests/components/sensor/test_foobot.py
@@ -0,0 +1,81 @@
+"""The tests for the Foobot sensor platform."""
+
+import re
+import asyncio
+from unittest.mock import MagicMock
+import pytest
+
+
+import homeassistant.components.sensor as sensor
+from homeassistant.components.sensor import foobot
+from homeassistant.const import (TEMP_CELSIUS)
+from homeassistant.exceptions import PlatformNotReady
+from homeassistant.setup import async_setup_component
+from tests.common import load_fixture
+
+VALID_CONFIG = {
+    'platform': 'foobot',
+    'token': 'adfdsfasd',
+    'username': 'example@example.com',
+}
+
+
+async def test_default_setup(hass, aioclient_mock):
+    """Test the default setup."""
+    aioclient_mock.get(re.compile('api.foobot.io/v2/owner/.*'),
+                       text=load_fixture('foobot_devices.json'))
+    aioclient_mock.get(re.compile('api.foobot.io/v2/device/.*'),
+                       text=load_fixture('foobot_data.json'))
+    assert await async_setup_component(hass, sensor.DOMAIN,
+                                       {'sensor': VALID_CONFIG})
+
+    metrics = {'co2': ['1232.0', 'ppm'],
+               'temperature': ['21.1', TEMP_CELSIUS],
+               'humidity': ['49.5', '%'],
+               'pm25': ['144.8', 'µg/m3'],
+               'voc': ['340.7', 'ppb'],
+               'index': ['138.9', '%']}
+
+    for name, value in metrics.items():
+        state = hass.states.get('sensor.foobot_happybot_%s' % name)
+        assert state.state == value[0]
+        assert state.attributes.get('unit_of_measurement') == value[1]
+
+
+async def test_setup_timeout_error(hass, aioclient_mock):
+    """Expected failures caused by a timeout in API response."""
+    fake_async_add_devices = MagicMock()
+
+    aioclient_mock.get(re.compile('api.foobot.io/v2/owner/.*'),
+                       exc=asyncio.TimeoutError())
+    with pytest.raises(PlatformNotReady):
+        await foobot.async_setup_platform(hass, {'sensor': VALID_CONFIG},
+                                          fake_async_add_devices)
+
+
+async def test_setup_permanent_error(hass, aioclient_mock):
+    """Expected failures caused by permanent errors in API response."""
+    fake_async_add_devices = MagicMock()
+
+    errors = [400, 401, 403]
+    for error in errors:
+        aioclient_mock.get(re.compile('api.foobot.io/v2/owner/.*'),
+                           status=error)
+        result = await foobot.async_setup_platform(hass,
+                                                   {'sensor': VALID_CONFIG},
+                                                   fake_async_add_devices)
+        assert result is None
+
+
+async def test_setup_temporary_error(hass, aioclient_mock):
+    """Expected failures caused by temporary errors in API response."""
+    fake_async_add_devices = MagicMock()
+
+    errors = [429, 500]
+    for error in errors:
+        aioclient_mock.get(re.compile('api.foobot.io/v2/owner/.*'),
+                           status=error)
+        with pytest.raises(PlatformNotReady):
+            await foobot.async_setup_platform(hass,
+                                              {'sensor': VALID_CONFIG},
+                                              fake_async_add_devices)
diff --git a/tests/fixtures/foobot_data.json b/tests/fixtures/foobot_data.json
new file mode 100644
index 0000000000000000000000000000000000000000..93518614c42e5d8da42b47289e1400b16ae358de
--- /dev/null
+++ b/tests/fixtures/foobot_data.json
@@ -0,0 +1,34 @@
+{
+    "uuid": "32463564765421243",
+    "start": 1518134963,
+    "end": 1518134963,
+    "sensors": [
+        "time",
+        "pm",
+        "tmp",
+        "hum",
+        "co2",
+        "voc",
+        "allpollu"
+    ],
+    "units": [
+        "s",
+        "ugm3",
+        "C",
+        "pc",
+        "ppm",
+        "ppb",
+        "%"
+    ],
+    "datapoints": [
+        [
+            1518134963,
+            144.76668,
+            21.064333,
+            49.474,
+            1232.0,
+            340.66666,
+            138.93651
+        ]
+    ]
+}
diff --git a/tests/fixtures/foobot_devices.json b/tests/fixtures/foobot_devices.json
new file mode 100644
index 0000000000000000000000000000000000000000..fffc8e151ccdd6c461c992d058520fe67552a325
--- /dev/null
+++ b/tests/fixtures/foobot_devices.json
@@ -0,0 +1,8 @@
+[
+    {
+        "uuid": "231425657665645342",
+        "userId": 6545342,
+        "mac": "A2D3F1",
+        "name": "Happybot"
+    }
+]