diff --git a/.coveragerc b/.coveragerc
index fc7a4691ef291897cfb25efc7c1087524cfd01fd..70d8f867e0e42db724c88ad35c4832b45bdc5f81 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -688,7 +688,14 @@ omit =
     homeassistant/components/telnet/switch.py
     homeassistant/components/temper/sensor.py
     homeassistant/components/tensorflow/image_processing.py
-    homeassistant/components/tesla/*
+    homeassistant/components/tesla/__init__.py
+    homeassistant/components/tesla/binary_sensor.py
+    homeassistant/components/tesla/climate.py
+    homeassistant/components/tesla/const.py
+    homeassistant/components/tesla/device_tracker.py
+    homeassistant/components/tesla/lock.py
+    homeassistant/components/tesla/sensor.py
+    homeassistant/components/tesla/switch.py
     homeassistant/components/tfiac/climate.py
     homeassistant/components/thermoworks_smoke/sensor.py
     homeassistant/components/thethingsnetwork/*
diff --git a/CODEOWNERS b/CODEOWNERS
index 4fbdca20686615750a112f75471d3da7d6fc651e..a7d1d346c5fe31886d773c20e3b8d9367afb2301 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -322,7 +322,7 @@ homeassistant/components/tahoma/* @philklei
 homeassistant/components/tautulli/* @ludeeus
 homeassistant/components/tellduslive/* @fredrike
 homeassistant/components/template/* @PhracturedBlue
-homeassistant/components/tesla/* @zabuldon
+homeassistant/components/tesla/* @zabuldon @alandtse
 homeassistant/components/tfiac/* @fredrike @mellado
 homeassistant/components/thethingsnetwork/* @fabaff
 homeassistant/components/threshold/* @fabaff
diff --git a/homeassistant/components/tesla/.translations/en.json b/homeassistant/components/tesla/.translations/en.json
new file mode 100644
index 0000000000000000000000000000000000000000..831406a0d63a939759b7c24658bd89de2ca6d6a9
--- /dev/null
+++ b/homeassistant/components/tesla/.translations/en.json
@@ -0,0 +1,30 @@
+{
+  "config": {
+    "error": {
+      "connection_error": "Error connecting; check network and retry",
+      "identifier_exists": "Email already registered",
+      "invalid_credentials": "Invalid credentials",
+      "unknown_error": "Unknown error, please report log info"
+    },
+    "step": {
+      "user": {
+        "data": {
+          "username": "Email Address",
+          "password": "Password"
+        },
+        "description": "Please enter your information.",
+        "title": "Tesla - Configuration"
+      }
+    },
+    "title": "Tesla"
+  },
+  "options": {
+    "step": {
+      "init": {
+        "data": {
+          "scan_interval": "Seconds between scans"
+        }
+      }
+    }
+  }
+}
diff --git a/homeassistant/components/tesla/__init__.py b/homeassistant/components/tesla/__init__.py
index a3d45eed01c6b683b9a7bd7426c5f50c65aea494..dbfe07271ee61ba878bb4324ba9706a4999283dc 100644
--- a/homeassistant/components/tesla/__init__.py
+++ b/homeassistant/components/tesla/__init__.py
@@ -1,21 +1,32 @@
 """Support for Tesla cars."""
+import asyncio
 from collections import defaultdict
 import logging
 
-from teslajsonpy import Controller as teslaAPI, TeslaException
+from teslajsonpy import Controller as TeslaAPI, TeslaException
 import voluptuous as vol
 
+from homeassistant.config_entries import SOURCE_IMPORT
 from homeassistant.const import (
     ATTR_BATTERY_LEVEL,
+    CONF_ACCESS_TOKEN,
     CONF_PASSWORD,
     CONF_SCAN_INTERVAL,
+    CONF_TOKEN,
     CONF_USERNAME,
 )
-from homeassistant.helpers import aiohttp_client, config_validation as cv, discovery
+from homeassistant.core import callback
+from homeassistant.helpers import aiohttp_client, config_validation as cv
 from homeassistant.helpers.entity import Entity
 from homeassistant.util import slugify
 
-from .const import DOMAIN, TESLA_COMPONENTS
+from .config_flow import (
+    CannotConnect,
+    InvalidAuth,
+    configured_instances,
+    validate_input,
+)
+from .const import DATA_LISTENER, DOMAIN, TESLA_COMPONENTS
 
 _LOGGER = logging.getLogger(__name__)
 
@@ -34,69 +45,144 @@ CONFIG_SCHEMA = vol.Schema(
     extra=vol.ALLOW_EXTRA,
 )
 
-NOTIFICATION_ID = "tesla_integration_notification"
-NOTIFICATION_TITLE = "Tesla integration setup"
+
+@callback
+def _async_save_tokens(hass, config_entry, access_token, refresh_token):
+    hass.config_entries.async_update_entry(
+        config_entry,
+        data={
+            **config_entry.data,
+            CONF_ACCESS_TOKEN: access_token,
+            CONF_TOKEN: refresh_token,
+        },
+    )
 
 
 async def async_setup(hass, base_config):
     """Set up of Tesla component."""
-    config = base_config.get(DOMAIN)
 
-    email = config.get(CONF_USERNAME)
-    password = config.get(CONF_PASSWORD)
-    update_interval = config.get(CONF_SCAN_INTERVAL)
-    if hass.data.get(DOMAIN) is None:
+    def _update_entry(email, data=None, options=None):
+        data = data or {}
+        options = options or {CONF_SCAN_INTERVAL: 300}
+        for entry in hass.config_entries.async_entries(DOMAIN):
+            if email != entry.title:
+                continue
+            hass.config_entries.async_update_entry(entry, data=data, options=options)
+
+    config = base_config.get(DOMAIN)
+    if not config:
+        return True
+    email = config[CONF_USERNAME]
+    password = config[CONF_PASSWORD]
+    scan_interval = config[CONF_SCAN_INTERVAL]
+    if email in configured_instances(hass):
         try:
-            websession = aiohttp_client.async_get_clientsession(hass)
-            controller = teslaAPI(
-                websession,
-                email=email,
-                password=password,
-                update_interval=update_interval,
-            )
-            await controller.connect(test_login=False)
-            hass.data[DOMAIN] = {"controller": controller, "devices": defaultdict(list)}
-            _LOGGER.debug("Connected to the Tesla API.")
-        except TeslaException as ex:
-            if ex.code == 401:
-                hass.components.persistent_notification.create(
-                    "Error:<br />Please check username and password."
-                    "You will need to restart Home Assistant after fixing.",
-                    title=NOTIFICATION_TITLE,
-                    notification_id=NOTIFICATION_ID,
-                )
-            else:
-                hass.components.persistent_notification.create(
-                    "Error:<br />Can't communicate with Tesla API.<br />"
-                    "Error code: {} Reason: {}"
-                    "You will need to restart Home Assistant after fixing."
-                    "".format(ex.code, ex.message),
-                    title=NOTIFICATION_TITLE,
-                    notification_id=NOTIFICATION_ID,
-                )
-            _LOGGER.error("Unable to communicate with Tesla API: %s", ex.message)
+            info = await validate_input(hass, config)
+        except (CannotConnect, InvalidAuth):
             return False
-    all_devices = controller.get_homeassistant_components()
+        _update_entry(
+            email,
+            data={
+                CONF_ACCESS_TOKEN: info[CONF_ACCESS_TOKEN],
+                CONF_TOKEN: info[CONF_TOKEN],
+            },
+            options={CONF_SCAN_INTERVAL: scan_interval},
+        )
+    else:
+        hass.async_create_task(
+            hass.config_entries.flow.async_init(
+                DOMAIN,
+                context={"source": SOURCE_IMPORT},
+                data={CONF_USERNAME: email, CONF_PASSWORD: password},
+            )
+        )
+        hass.data.setdefault(DOMAIN, {})
+        hass.data[DOMAIN][email] = {CONF_SCAN_INTERVAL: scan_interval}
+    return True
+
+
+async def async_setup_entry(hass, config_entry):
+    """Set up Tesla as config entry."""
+
+    hass.data.setdefault(DOMAIN, {})
+    config = config_entry.data
+    websession = aiohttp_client.async_get_clientsession(hass)
+    email = config_entry.title
+    if email in hass.data[DOMAIN] and CONF_SCAN_INTERVAL in hass.data[DOMAIN][email]:
+        scan_interval = hass.data[DOMAIN][email][CONF_SCAN_INTERVAL]
+        hass.config_entries.async_update_entry(
+            config_entry, options={CONF_SCAN_INTERVAL: scan_interval}
+        )
+        hass.data[DOMAIN].pop(email)
+    try:
+        controller = TeslaAPI(
+            websession,
+            refresh_token=config[CONF_TOKEN],
+            update_interval=config_entry.options.get(CONF_SCAN_INTERVAL, 300),
+        )
+        (refresh_token, access_token) = await controller.connect()
+    except TeslaException as ex:
+        _LOGGER.error("Unable to communicate with Tesla API: %s", ex.message)
+        return False
+    _async_save_tokens(hass, config_entry, access_token, refresh_token)
+    entry_data = hass.data[DOMAIN][config_entry.entry_id] = {
+        "controller": controller,
+        "devices": defaultdict(list),
+        DATA_LISTENER: [config_entry.add_update_listener(update_listener)],
+    }
+    _LOGGER.debug("Connected to the Tesla API.")
+    all_devices = entry_data["controller"].get_homeassistant_components()
+
     if not all_devices:
         return False
 
     for device in all_devices:
-        hass.data[DOMAIN]["devices"][device.hass_type].append(device)
+        entry_data["devices"][device.hass_type].append(device)
 
     for component in TESLA_COMPONENTS:
+        _LOGGER.debug("Loading %s", component)
         hass.async_create_task(
-            discovery.async_load_platform(hass, component, DOMAIN, {}, base_config)
+            hass.config_entries.async_forward_entry_setup(config_entry, component)
         )
     return True
 
 
+async def async_unload_entry(hass, config_entry) -> bool:
+    """Unload a config entry."""
+    await asyncio.gather(
+        *[
+            hass.config_entries.async_forward_entry_unload(config_entry, component)
+            for component in TESLA_COMPONENTS
+        ]
+    )
+    for listener in hass.data[DOMAIN][config_entry.entry_id][DATA_LISTENER]:
+        listener()
+    username = config_entry.title
+    hass.data[DOMAIN].pop(config_entry.entry_id)
+    _LOGGER.debug("Unloaded entry for %s", username)
+    return True
+
+
+async def update_listener(hass, config_entry):
+    """Update when config_entry options update."""
+    controller = hass.data[DOMAIN][config_entry.entry_id]["controller"]
+    old_update_interval = controller.update_interval
+    controller.update_interval = config_entry.options.get(CONF_SCAN_INTERVAL)
+    _LOGGER.debug(
+        "Changing scan_interval from %s to %s",
+        old_update_interval,
+        controller.update_interval,
+    )
+
+
 class TeslaDevice(Entity):
     """Representation of a Tesla device."""
 
-    def __init__(self, tesla_device, controller):
+    def __init__(self, tesla_device, controller, config_entry):
         """Initialise the Tesla device."""
         self.tesla_device = tesla_device
         self.controller = controller
+        self.config_entry = config_entry
         self._name = self.tesla_device.name
         self.tesla_id = slugify(self.tesla_device.uniq_name)
         self._attributes = {}
@@ -124,6 +210,17 @@ class TeslaDevice(Entity):
             attr[ATTR_BATTERY_LEVEL] = self.tesla_device.battery_level()
         return attr
 
+    @property
+    def device_info(self):
+        """Return the device_info of the device."""
+        return {
+            "identifiers": {(DOMAIN, self.tesla_device.id())},
+            "name": self.tesla_device.car_name(),
+            "manufacturer": "Tesla",
+            "model": self.tesla_device.car_type,
+            "sw_version": self.tesla_device.car_version,
+        }
+
     async def async_added_to_hass(self):
         """Register state update callback."""
         pass
@@ -134,4 +231,10 @@ class TeslaDevice(Entity):
 
     async def async_update(self):
         """Update the state of the device."""
+        if self.controller.is_token_refreshed():
+            (refresh_token, access_token) = self.controller.get_tokens()
+            _async_save_tokens(
+                self.hass, self.config_entry, access_token, refresh_token
+            )
+            _LOGGER.debug("Saving new tokens in config_entry")
         await self.tesla_device.async_update()
diff --git a/homeassistant/components/tesla/binary_sensor.py b/homeassistant/components/tesla/binary_sensor.py
index 738533a9b5685a289207e5d07f8a929d0287c51a..8f610d960b33bfd0a0c9e46f3f146a431f2f2e0b 100644
--- a/homeassistant/components/tesla/binary_sensor.py
+++ b/homeassistant/components/tesla/binary_sensor.py
@@ -8,21 +8,35 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
 _LOGGER = logging.getLogger(__name__)
 
 
-async def async_setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
     """Set up the Tesla binary sensor."""
-    devices = [
-        TeslaBinarySensor(device, hass.data[TESLA_DOMAIN]["controller"], "connectivity")
-        for device in hass.data[TESLA_DOMAIN]["devices"]["binary_sensor"]
-    ]
-    add_entities(devices, True)
+    pass
+
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+    """Set up the Tesla binary_sensors by config_entry."""
+    async_add_entities(
+        [
+            TeslaBinarySensor(
+                device,
+                hass.data[TESLA_DOMAIN][config_entry.entry_id]["controller"],
+                "connectivity",
+                config_entry,
+            )
+            for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"][
+                "binary_sensor"
+            ]
+        ],
+        True,
+    )
 
 
 class TeslaBinarySensor(TeslaDevice, BinarySensorDevice):
     """Implement an Tesla binary sensor for parking and charger."""
 
-    def __init__(self, tesla_device, controller, sensor_type):
+    def __init__(self, tesla_device, controller, sensor_type, config_entry):
         """Initialise of a Tesla binary sensor."""
-        super().__init__(tesla_device, controller)
+        super().__init__(tesla_device, controller, config_entry)
         self._state = False
         self._sensor_type = sensor_type
 
diff --git a/homeassistant/components/tesla/climate.py b/homeassistant/components/tesla/climate.py
index 85fd8a8e258dda0af025e6b1a69cb50f80ba1166..d3c87035c9ca9d0a4f58c0ae3009d7eac1ef2370 100644
--- a/homeassistant/components/tesla/climate.py
+++ b/homeassistant/components/tesla/climate.py
@@ -16,21 +16,34 @@ _LOGGER = logging.getLogger(__name__)
 SUPPORT_HVAC = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
 
 
-async def async_setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
     """Set up the Tesla climate platform."""
-    devices = [
-        TeslaThermostat(device, hass.data[TESLA_DOMAIN]["controller"])
-        for device in hass.data[TESLA_DOMAIN]["devices"]["climate"]
-    ]
-    add_entities(devices, True)
+    pass
+
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+    """Set up the Tesla binary_sensors by config_entry."""
+    async_add_entities(
+        [
+            TeslaThermostat(
+                device,
+                hass.data[TESLA_DOMAIN][config_entry.entry_id]["controller"],
+                config_entry,
+            )
+            for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"][
+                "climate"
+            ]
+        ],
+        True,
+    )
 
 
 class TeslaThermostat(TeslaDevice, ClimateDevice):
     """Representation of a Tesla climate."""
 
-    def __init__(self, tesla_device, controller):
+    def __init__(self, tesla_device, controller, config_entry):
         """Initialize the Tesla device."""
-        super().__init__(tesla_device, controller)
+        super().__init__(tesla_device, controller, config_entry)
         self._target_temperature = None
         self._temperature = None
 
diff --git a/homeassistant/components/tesla/config_flow.py b/homeassistant/components/tesla/config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..2d2bc0158d2b888872e1f0863c984ea7733dc4f5
--- /dev/null
+++ b/homeassistant/components/tesla/config_flow.py
@@ -0,0 +1,143 @@
+"""Tesla Config Flow."""
+import logging
+
+from teslajsonpy import Controller as TeslaAPI, TeslaException
+import voluptuous as vol
+
+from homeassistant import config_entries, core, exceptions
+from homeassistant.const import (
+    CONF_ACCESS_TOKEN,
+    CONF_PASSWORD,
+    CONF_SCAN_INTERVAL,
+    CONF_TOKEN,
+    CONF_USERNAME,
+)
+from homeassistant.core import callback
+from homeassistant.helpers import aiohttp_client, config_validation as cv
+
+from .const import DOMAIN
+
+_LOGGER = logging.getLogger(__name__)
+
+DATA_SCHEMA = vol.Schema(
+    {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
+)
+
+
+@callback
+def configured_instances(hass):
+    """Return a set of configured Tesla instances."""
+    return set(entry.title for entry in hass.config_entries.async_entries(DOMAIN))
+
+
+class TeslaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
+    """Handle a config flow for Tesla."""
+
+    VERSION = 1
+    CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
+
+    async def async_step_import(self, import_config):
+        """Import a config entry from configuration.yaml."""
+        return await self.async_step_user(import_config)
+
+    async def async_step_user(self, user_input=None):
+        """Handle the start of the config flow."""
+
+        if not user_input:
+            return self.async_show_form(
+                step_id="user",
+                data_schema=DATA_SCHEMA,
+                errors={},
+                description_placeholders={},
+            )
+
+        if user_input[CONF_USERNAME] in configured_instances(self.hass):
+            return self.async_show_form(
+                step_id="user",
+                data_schema=DATA_SCHEMA,
+                errors={CONF_USERNAME: "identifier_exists"},
+                description_placeholders={},
+            )
+
+        try:
+            info = await validate_input(self.hass, user_input)
+        except CannotConnect:
+            return self.async_show_form(
+                step_id="user",
+                data_schema=DATA_SCHEMA,
+                errors={"base": "connection_error"},
+                description_placeholders={},
+            )
+        except InvalidAuth:
+            return self.async_show_form(
+                step_id="user",
+                data_schema=DATA_SCHEMA,
+                errors={"base": "invalid_credentials"},
+                description_placeholders={},
+            )
+        return self.async_create_entry(title=user_input[CONF_USERNAME], data=info)
+
+    @staticmethod
+    @callback
+    def async_get_options_flow(config_entry):
+        """Get the options flow for this handler."""
+        return OptionsFlowHandler(config_entry)
+
+
+class OptionsFlowHandler(config_entries.OptionsFlow):
+    """Handle a option flow for Tesla."""
+
+    def __init__(self, config_entry: config_entries.ConfigEntry):
+        """Initialize options flow."""
+        self.config_entry = config_entry
+
+    async def async_step_init(self, user_input=None):
+        """Handle options flow."""
+        if user_input is not None:
+            return self.async_create_entry(title="", data=user_input)
+
+        data_schema = vol.Schema(
+            {
+                vol.Optional(
+                    CONF_SCAN_INTERVAL,
+                    default=self.config_entry.options.get(CONF_SCAN_INTERVAL, 300),
+                ): vol.All(cv.positive_int, vol.Clamp(min=300))
+            }
+        )
+        return self.async_show_form(step_id="init", data_schema=data_schema)
+
+
+async def validate_input(hass: core.HomeAssistant, data):
+    """Validate the user input allows us to connect.
+
+    Data has the keys from DATA_SCHEMA with values provided by the user.
+    """
+
+    config = {}
+    websession = aiohttp_client.async_get_clientsession(hass)
+    try:
+        controller = TeslaAPI(
+            websession,
+            email=data[CONF_USERNAME],
+            password=data[CONF_PASSWORD],
+            update_interval=300,
+        )
+        (config[CONF_TOKEN], config[CONF_ACCESS_TOKEN]) = await controller.connect(
+            test_login=True
+        )
+    except TeslaException as ex:
+        if ex.code == 401:
+            _LOGGER.error("Invalid credentials: %s", ex)
+            raise InvalidAuth()
+        _LOGGER.error("Unable to communicate with Tesla API: %s", ex)
+        raise CannotConnect()
+    _LOGGER.debug("Credentials successfully connected to the Tesla API")
+    return config
+
+
+class CannotConnect(exceptions.HomeAssistantError):
+    """Error to indicate we cannot connect."""
+
+
+class InvalidAuth(exceptions.HomeAssistantError):
+    """Error to indicate there is invalid auth."""
diff --git a/homeassistant/components/tesla/device_tracker.py b/homeassistant/components/tesla/device_tracker.py
index c205cc587eba4e4b453249665926821fa7a68b7c..08e5d58ba6e6efb1a2b97e407adff6540a455776 100644
--- a/homeassistant/components/tesla/device_tracker.py
+++ b/homeassistant/components/tesla/device_tracker.py
@@ -1,45 +1,70 @@
 """Support for tracking Tesla cars."""
 import logging
 
-from homeassistant.helpers.event import async_track_utc_time_change
-from homeassistant.util import slugify
+from homeassistant.components.device_tracker import SOURCE_TYPE_GPS
+from homeassistant.components.device_tracker.config_entry import TrackerEntity
 
-from . import DOMAIN as TESLA_DOMAIN
+from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
 
 _LOGGER = logging.getLogger(__name__)
 
 
-async def async_setup_scanner(hass, config, async_see, discovery_info=None):
-    """Set up the Tesla tracker."""
-    tracker = TeslaDeviceTracker(
-        hass, config, async_see, hass.data[TESLA_DOMAIN]["devices"]["devices_tracker"]
-    )
-    await tracker.update_info()
-    async_track_utc_time_change(hass, tracker.update_info, second=range(0, 60, 30))
-    return True
+async def async_setup_entry(hass, config_entry, async_add_entities):
+    """Set up the Tesla binary_sensors by config_entry."""
+    entities = [
+        TeslaDeviceEntity(
+            device,
+            hass.data[TESLA_DOMAIN][config_entry.entry_id]["controller"],
+            config_entry,
+        )
+        for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"][
+            "devices_tracker"
+        ]
+    ]
+    async_add_entities(entities, True)
 
 
-class TeslaDeviceTracker:
+class TeslaDeviceEntity(TeslaDevice, TrackerEntity):
     """A class representing a Tesla device."""
 
-    def __init__(self, hass, config, see, tesla_devices):
+    def __init__(self, tesla_device, controller, config_entry):
         """Initialize the Tesla device scanner."""
-        self.hass = hass
-        self.see = see
-        self.devices = tesla_devices
+        super().__init__(tesla_device, controller, config_entry)
+        self._latitude = None
+        self._longitude = None
+        self._attributes = {"trackr_id": self.unique_id}
+        self._listener = None
 
-    async def update_info(self, now=None):
+    async def async_update(self):
         """Update the device info."""
-        for device in self.devices:
-            await device.async_update()
-            name = device.name
-            _LOGGER.debug("Updating device position: %s", name)
-            dev_id = slugify(device.uniq_name)
-            location = device.get_location()
-            if location:
-                lat = location["latitude"]
-                lon = location["longitude"]
-                attrs = {"trackr_id": dev_id, "id": dev_id, "name": name}
-                await self.see(
-                    dev_id=dev_id, host_name=name, gps=(lat, lon), attributes=attrs
-                )
+        _LOGGER.debug("Updating device position: %s", self.name)
+        await super().async_update()
+        location = self.tesla_device.get_location()
+        if location:
+            self._latitude = location["latitude"]
+            self._longitude = location["longitude"]
+            self._attributes = {
+                "trackr_id": self.unique_id,
+                "heading": location["heading"],
+                "speed": location["speed"],
+            }
+
+    @property
+    def latitude(self) -> float:
+        """Return latitude value of the device."""
+        return self._latitude
+
+    @property
+    def longitude(self) -> float:
+        """Return longitude value of the device."""
+        return self._longitude
+
+    @property
+    def should_poll(self):
+        """Return whether polling is needed."""
+        return True
+
+    @property
+    def source_type(self):
+        """Return the source type, eg gps or router, of the device."""
+        return SOURCE_TYPE_GPS
diff --git a/homeassistant/components/tesla/lock.py b/homeassistant/components/tesla/lock.py
index 5e97602357df8f158cf07ec01f07b7a46674f8d5..33eed8cf7c1e23ac29622c547ee28d5411fa5f4e 100644
--- a/homeassistant/components/tesla/lock.py
+++ b/homeassistant/components/tesla/lock.py
@@ -9,22 +9,31 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
 _LOGGER = logging.getLogger(__name__)
 
 
-async def async_setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
     """Set up the Tesla lock platform."""
-    devices = [
-        TeslaLock(device, hass.data[TESLA_DOMAIN]["controller"])
-        for device in hass.data[TESLA_DOMAIN]["devices"]["lock"]
+    pass
+
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+    """Set up the Tesla binary_sensors by config_entry."""
+    entities = [
+        TeslaLock(
+            device,
+            hass.data[TESLA_DOMAIN][config_entry.entry_id]["controller"],
+            config_entry,
+        )
+        for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"]["lock"]
     ]
-    add_entities(devices, True)
+    async_add_entities(entities, True)
 
 
 class TeslaLock(TeslaDevice, LockDevice):
     """Representation of a Tesla door lock."""
 
-    def __init__(self, tesla_device, controller):
+    def __init__(self, tesla_device, controller, config_entry):
         """Initialise of the lock."""
         self._state = None
-        super().__init__(tesla_device, controller)
+        super().__init__(tesla_device, controller, config_entry)
 
     async def async_lock(self, **kwargs):
         """Send the lock command."""
diff --git a/homeassistant/components/tesla/manifest.json b/homeassistant/components/tesla/manifest.json
index a2021092413011bef33fb1d880de625e81949785..4a869ab0a4138a6b024af30dfefb0c41294dee9f 100644
--- a/homeassistant/components/tesla/manifest.json
+++ b/homeassistant/components/tesla/manifest.json
@@ -1,8 +1,9 @@
 {
   "domain": "tesla",
   "name": "Tesla",
+  "config_flow": true,
   "documentation": "https://www.home-assistant.io/integrations/tesla",
   "requirements": ["teslajsonpy==0.2.0"],
   "dependencies": [],
-  "codeowners": ["@zabuldon"]
+  "codeowners": ["@zabuldon", "@alandtse"]
 }
diff --git a/homeassistant/components/tesla/sensor.py b/homeassistant/components/tesla/sensor.py
index 1cce37f232a5e1a3ae0a2e0d67ef604efae48525..d93d3fa45d6ec158af6fdf6418c90cc61d49ef4c 100644
--- a/homeassistant/components/tesla/sensor.py
+++ b/homeassistant/components/tesla/sensor.py
@@ -14,30 +14,34 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
 _LOGGER = logging.getLogger(__name__)
 
 
-async def async_setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
     """Set up the Tesla sensor platform."""
-    controller = hass.data[TESLA_DOMAIN]["devices"]["controller"]
-    devices = []
+    pass
 
-    for device in hass.data[TESLA_DOMAIN]["devices"]["sensor"]:
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+    """Set up the Tesla binary_sensors by config_entry."""
+    controller = hass.data[TESLA_DOMAIN][config_entry.entry_id]["controller"]
+    entities = []
+    for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"]["sensor"]:
         if device.bin_type == 0x4:
-            devices.append(TeslaSensor(device, controller, "inside"))
-            devices.append(TeslaSensor(device, controller, "outside"))
+            entities.append(TeslaSensor(device, controller, config_entry, "inside"))
+            entities.append(TeslaSensor(device, controller, config_entry, "outside"))
         elif device.bin_type in [0xA, 0xB, 0x5]:
-            devices.append(TeslaSensor(device, controller))
-    add_entities(devices, True)
+            entities.append(TeslaSensor(device, controller, config_entry))
+    async_add_entities(entities, True)
 
 
 class TeslaSensor(TeslaDevice, Entity):
     """Representation of Tesla sensors."""
 
-    def __init__(self, tesla_device, controller, sensor_type=None):
+    def __init__(self, tesla_device, controller, config_entry, sensor_type=None):
         """Initialize of the sensor."""
         self.current_value = None
         self._unit = None
         self.last_changed_time = None
         self.type = sensor_type
-        super().__init__(tesla_device, controller)
+        super().__init__(tesla_device, controller, config_entry)
 
         if self.type:
             self._name = f"{self.tesla_device.name} ({self.type})"
diff --git a/homeassistant/components/tesla/strings.json b/homeassistant/components/tesla/strings.json
new file mode 100644
index 0000000000000000000000000000000000000000..831406a0d63a939759b7c24658bd89de2ca6d6a9
--- /dev/null
+++ b/homeassistant/components/tesla/strings.json
@@ -0,0 +1,30 @@
+{
+  "config": {
+    "error": {
+      "connection_error": "Error connecting; check network and retry",
+      "identifier_exists": "Email already registered",
+      "invalid_credentials": "Invalid credentials",
+      "unknown_error": "Unknown error, please report log info"
+    },
+    "step": {
+      "user": {
+        "data": {
+          "username": "Email Address",
+          "password": "Password"
+        },
+        "description": "Please enter your information.",
+        "title": "Tesla - Configuration"
+      }
+    },
+    "title": "Tesla"
+  },
+  "options": {
+    "step": {
+      "init": {
+        "data": {
+          "scan_interval": "Seconds between scans"
+        }
+      }
+    }
+  }
+}
diff --git a/homeassistant/components/tesla/switch.py b/homeassistant/components/tesla/switch.py
index 5f432875aebc0b4576da45f3c37baeb2b78de758..3fc424e390da3eb2f2713d57c9f18d3f4dbf6b1d 100644
--- a/homeassistant/components/tesla/switch.py
+++ b/homeassistant/components/tesla/switch.py
@@ -9,26 +9,31 @@ from . import DOMAIN as TESLA_DOMAIN, TeslaDevice
 _LOGGER = logging.getLogger(__name__)
 
 
-async def async_setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
     """Set up the Tesla switch platform."""
-    controller = hass.data[TESLA_DOMAIN]["controller"]
-    devices = []
-    for device in hass.data[TESLA_DOMAIN]["devices"]["switch"]:
+    pass
+
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+    """Set up the Tesla binary_sensors by config_entry."""
+    controller = hass.data[TESLA_DOMAIN][config_entry.entry_id]["controller"]
+    entities = []
+    for device in hass.data[TESLA_DOMAIN][config_entry.entry_id]["devices"]["switch"]:
         if device.bin_type == 0x8:
-            devices.append(ChargerSwitch(device, controller))
-            devices.append(UpdateSwitch(device, controller))
+            entities.append(ChargerSwitch(device, controller, config_entry))
+            entities.append(UpdateSwitch(device, controller, config_entry))
         elif device.bin_type == 0x9:
-            devices.append(RangeSwitch(device, controller))
-    add_entities(devices, True)
+            entities.append(RangeSwitch(device, controller, config_entry))
+    async_add_entities(entities, True)
 
 
 class ChargerSwitch(TeslaDevice, SwitchDevice):
     """Representation of a Tesla charger switch."""
 
-    def __init__(self, tesla_device, controller):
+    def __init__(self, tesla_device, controller, config_entry):
         """Initialise of the switch."""
         self._state = None
-        super().__init__(tesla_device, controller)
+        super().__init__(tesla_device, controller, config_entry)
 
     async def async_turn_on(self, **kwargs):
         """Send the on command."""
@@ -55,10 +60,10 @@ class ChargerSwitch(TeslaDevice, SwitchDevice):
 class RangeSwitch(TeslaDevice, SwitchDevice):
     """Representation of a Tesla max range charging switch."""
 
-    def __init__(self, tesla_device, controller):
+    def __init__(self, tesla_device, controller, config_entry):
         """Initialise the switch."""
         self._state = None
-        super().__init__(tesla_device, controller)
+        super().__init__(tesla_device, controller, config_entry)
 
     async def async_turn_on(self, **kwargs):
         """Send the on command."""
@@ -85,11 +90,11 @@ class RangeSwitch(TeslaDevice, SwitchDevice):
 class UpdateSwitch(TeslaDevice, SwitchDevice):
     """Representation of a Tesla update switch."""
 
-    def __init__(self, tesla_device, controller):
+    def __init__(self, tesla_device, controller, config_entry):
         """Initialise the switch."""
         self._state = None
         tesla_device.type = "update switch"
-        super().__init__(tesla_device, controller)
+        super().__init__(tesla_device, controller, config_entry)
         self._name = self._name.replace("charger", "update")
         self.tesla_id = self.tesla_id.replace("charger", "update")
 
diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py
index 2b3940000e79630e854fae64e70de48626dabe64..88ff92a57b05930ed01ba188d25507f74e877fc5 100644
--- a/homeassistant/generated/config_flows.py
+++ b/homeassistant/generated/config_flows.py
@@ -73,6 +73,7 @@ FLOWS = [
     "sonos",
     "starline",
     "tellduslive",
+    "tesla",
     "toon",
     "tplink",
     "traccar",
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index e5361e7464e80a015a8bfaf353ad3dc51ec0d155..2b7101dfa258204762e0ff51d755ec905e3a0ae5 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -598,6 +598,9 @@ sunwatcher==0.2.1
 # homeassistant.components.tellduslive
 tellduslive==0.10.10
 
+# homeassistant.components.tesla
+teslajsonpy==0.2.0
+
 # homeassistant.components.toon
 toonapilib==3.2.4
 
diff --git a/tests/components/tesla/__init__.py b/tests/components/tesla/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..89b1e1c0c549f7afadf86a92637196e233a43efa
--- /dev/null
+++ b/tests/components/tesla/__init__.py
@@ -0,0 +1 @@
+"""Tests for the Tesla integration."""
diff --git a/tests/components/tesla/test_config_flow.py b/tests/components/tesla/test_config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6eeff54a50737d0050e25404c9caa48fb94cbcc
--- /dev/null
+++ b/tests/components/tesla/test_config_flow.py
@@ -0,0 +1,160 @@
+"""Test the Tesla config flow."""
+from unittest.mock import patch
+
+from teslajsonpy import TeslaException
+
+from homeassistant import config_entries, data_entry_flow, setup
+from homeassistant.components.tesla.const import DOMAIN
+from homeassistant.const import (
+    CONF_ACCESS_TOKEN,
+    CONF_PASSWORD,
+    CONF_SCAN_INTERVAL,
+    CONF_TOKEN,
+    CONF_USERNAME,
+)
+
+from tests.common import MockConfigEntry, mock_coro
+
+
+async def test_form(hass):
+    """Test we get the form."""
+    await setup.async_setup_component(hass, "persistent_notification", {})
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+    assert result["type"] == "form"
+    assert result["errors"] == {}
+
+    with patch(
+        "homeassistant.components.tesla.config_flow.TeslaAPI.connect",
+        return_value=mock_coro(("test-refresh-token", "test-access-token")),
+    ), patch(
+        "homeassistant.components.tesla.async_setup", return_value=mock_coro(True)
+    ) as mock_setup, patch(
+        "homeassistant.components.tesla.async_setup_entry", return_value=mock_coro(True)
+    ) as mock_setup_entry:
+        result2 = await hass.config_entries.flow.async_configure(
+            result["flow_id"], {CONF_PASSWORD: "test", CONF_USERNAME: "test@email.com"}
+        )
+
+    assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+    assert result2["title"] == "test@email.com"
+    assert result2["data"] == {
+        "token": "test-refresh-token",
+        "access_token": "test-access-token",
+    }
+    await hass.async_block_till_done()
+    assert len(mock_setup.mock_calls) == 1
+    assert len(mock_setup_entry.mock_calls) == 1
+
+
+async def test_form_invalid_auth(hass):
+    """Test we handle invalid auth."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+
+    with patch(
+        "homeassistant.components.tesla.config_flow.TeslaAPI.connect",
+        side_effect=TeslaException(401),
+    ):
+        result2 = await hass.config_entries.flow.async_configure(
+            result["flow_id"],
+            {CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"},
+        )
+
+    assert result2["type"] == "form"
+    assert result2["errors"] == {"base": "invalid_credentials"}
+
+
+async def test_form_cannot_connect(hass):
+    """Test we handle cannot connect error."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+
+    with patch(
+        "homeassistant.components.tesla.config_flow.TeslaAPI.connect",
+        side_effect=TeslaException(code=404),
+    ):
+        result2 = await hass.config_entries.flow.async_configure(
+            result["flow_id"],
+            {CONF_PASSWORD: "test-password", CONF_USERNAME: "test-username"},
+        )
+
+    assert result2["type"] == "form"
+    assert result2["errors"] == {"base": "connection_error"}
+
+
+async def test_form_repeat_identifier(hass):
+    """Test we handle repeat identifiers."""
+    entry = MockConfigEntry(domain=DOMAIN, title="test-username", data={}, options=None)
+    entry.add_to_hass(hass)
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": config_entries.SOURCE_USER}
+    )
+    with patch(
+        "homeassistant.components.tesla.config_flow.TeslaAPI.connect",
+        return_value=mock_coro(("test-refresh-token", "test-access-token")),
+    ):
+        result2 = await hass.config_entries.flow.async_configure(
+            result["flow_id"],
+            {CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"},
+        )
+
+    assert result2["type"] == "form"
+    assert result2["errors"] == {CONF_USERNAME: "identifier_exists"}
+
+
+async def test_import(hass):
+    """Test import step."""
+
+    with patch(
+        "homeassistant.components.tesla.config_flow.TeslaAPI.connect",
+        return_value=mock_coro(("test-refresh-token", "test-access-token")),
+    ):
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN,
+            context={"source": config_entries.SOURCE_IMPORT},
+            data={CONF_PASSWORD: "test-password", CONF_USERNAME: "test-username"},
+        )
+    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+    assert result["title"] == "test-username"
+    assert result["data"][CONF_ACCESS_TOKEN] == "test-access-token"
+    assert result["data"][CONF_TOKEN] == "test-refresh-token"
+    assert result["description_placeholders"] is None
+
+
+async def test_option_flow(hass):
+    """Test config flow options."""
+    entry = MockConfigEntry(domain=DOMAIN, data={}, options=None)
+    entry.add_to_hass(hass)
+
+    result = await hass.config_entries.options.flow.async_init(entry.entry_id)
+
+    assert result["type"] == "form"
+    assert result["step_id"] == "init"
+
+    result = await hass.config_entries.options.flow.async_configure(
+        result["flow_id"], user_input={CONF_SCAN_INTERVAL: 350}
+    )
+    assert result["type"] == "create_entry"
+    assert result["data"] == {CONF_SCAN_INTERVAL: 350}
+
+
+async def test_option_flow_input_floor(hass):
+    """Test config flow options."""
+    entry = MockConfigEntry(domain=DOMAIN, data={}, options=None)
+    entry.add_to_hass(hass)
+
+    result = await hass.config_entries.options.flow.async_init(entry.entry_id)
+
+    assert result["type"] == "form"
+    assert result["step_id"] == "init"
+
+    result = await hass.config_entries.options.flow.async_configure(
+        result["flow_id"], user_input={CONF_SCAN_INTERVAL: 1}
+    )
+    assert result["type"] == "create_entry"
+    assert result["data"] == {CONF_SCAN_INTERVAL: 300}