diff --git a/.coveragerc b/.coveragerc
index 5f9ccd9e01438522679404d0172e18bbf7118700..829915400bda41f8b703f12cf0697d98afb40325 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -269,7 +269,10 @@ omit =
     homeassistant/components/enphase_envoy/__init__.py
     homeassistant/components/enphase_envoy/sensor.py
     homeassistant/components/entur_public_transport/*
-    homeassistant/components/environment_canada/*
+    homeassistant/components/environment_canada/__init__.py
+    homeassistant/components/environment_canada/camera.py
+    homeassistant/components/environment_canada/sensor.py
+    homeassistant/components/environment_canada/weather.py
     homeassistant/components/envirophat/sensor.py
     homeassistant/components/envisalink/*
     homeassistant/components/ephember/climate.py
diff --git a/CODEOWNERS b/CODEOWNERS
index a4756f961be0c8e30f96c47067575022a0125c48..e3a4cb00c83f4eb2f49a306427537a1ac7292be3 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -151,7 +151,7 @@ homeassistant/components/enigma2/* @fbradyirl
 homeassistant/components/enocean/* @bdurrer
 homeassistant/components/enphase_envoy/* @gtdiehl
 homeassistant/components/entur_public_transport/* @hfurubotten
-homeassistant/components/environment_canada/* @michaeldavie
+homeassistant/components/environment_canada/* @gwww @michaeldavie
 homeassistant/components/ephember/* @ttroy50
 homeassistant/components/epson/* @pszafer
 homeassistant/components/epsonworkforce/* @ThaStealth
diff --git a/homeassistant/components/environment_canada/__init__.py b/homeassistant/components/environment_canada/__init__.py
index 356e18fe23fd4dc9ef41e328ed105d08c920092a..0821059fcdfc08468fb30240c64858c59f93110c 100644
--- a/homeassistant/components/environment_canada/__init__.py
+++ b/homeassistant/components/environment_canada/__init__.py
@@ -1 +1,79 @@
-"""A component for Environment Canada weather."""
+"""The Environment Canada (EC) component."""
+from functools import partial
+import logging
+
+from env_canada import ECData, ECRadar
+
+from homeassistant.config_entries import SOURCE_IMPORT
+from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
+
+from .const import CONF_LANGUAGE, CONF_STATION, DOMAIN
+
+PLATFORMS = ["camera", "sensor", "weather"]
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup_entry(hass, config_entry):
+    """Set up EC as config entry."""
+    lat = config_entry.data.get(CONF_LATITUDE)
+    lon = config_entry.data.get(CONF_LONGITUDE)
+    station = config_entry.data.get(CONF_STATION)
+    lang = config_entry.data.get(CONF_LANGUAGE, "English")
+
+    weather_api = {}
+
+    weather_init = partial(
+        ECData, station_id=station, coordinates=(lat, lon), language=lang.lower()
+    )
+    weather_data = await hass.async_add_executor_job(weather_init)
+    weather_api["weather_data"] = weather_data
+
+    radar_init = partial(ECRadar, coordinates=(lat, lon))
+    radar_data = await hass.async_add_executor_job(radar_init)
+    weather_api["radar_data"] = radar_data
+    await hass.async_add_executor_job(radar_data.get_loop)
+
+    hass.data.setdefault(DOMAIN, {})
+    hass.data[DOMAIN][config_entry.entry_id] = weather_api
+
+    hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
+
+    return True
+
+
+async def async_unload_entry(hass, config_entry):
+    """Unload a config entry."""
+    unload_ok = await hass.config_entries.async_unload_platforms(
+        config_entry, PLATFORMS
+    )
+
+    hass.data[DOMAIN].pop(config_entry.entry_id)
+
+    return unload_ok
+
+
+def trigger_import(hass, config):
+    """Trigger a import of YAML config into a config_entry."""
+    _LOGGER.warning(
+        "Environment Canada YAML configuration is deprecated; your YAML configuration "
+        "has been imported into the UI and can be safely removed"
+    )
+    if not config.get(CONF_LANGUAGE):
+        config[CONF_LANGUAGE] = "English"
+
+    data = {}
+    for key in (
+        CONF_STATION,
+        CONF_LATITUDE,
+        CONF_LONGITUDE,
+        CONF_LANGUAGE,
+    ):  # pylint: disable=consider-using-tuple
+        if config.get(key):
+            data[key] = config[key]
+
+    hass.async_create_task(
+        hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": SOURCE_IMPORT}, data=data
+        )
+    )
diff --git a/homeassistant/components/environment_canada/camera.py b/homeassistant/components/environment_canada/camera.py
index ecd0c562d167f0166bafc0c0e221235ef1b9912a..0c8e1de6107187e16a3c0fcb51bb308f82a47a73 100644
--- a/homeassistant/components/environment_canada/camera.py
+++ b/homeassistant/components/environment_canada/camera.py
@@ -2,8 +2,10 @@
 from __future__ import annotations
 
 import datetime
+import logging
 
-from env_canada import ECRadar
+from env_canada import get_station_coords
+from requests.exceptions import ConnectionError as RequestsConnectionError
 import voluptuous as vol
 
 from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
@@ -16,15 +18,17 @@ from homeassistant.const import (
 import homeassistant.helpers.config_validation as cv
 from homeassistant.util import Throttle
 
-ATTR_UPDATED = "updated"
+from . import trigger_import
+from .const import CONF_ATTRIBUTION, CONF_STATION, DOMAIN
 
-CONF_ATTRIBUTION = "Data provided by Environment Canada"
-CONF_STATION = "station"
 CONF_LOOP = "loop"
 CONF_PRECIP_TYPE = "precip_type"
+ATTR_UPDATED = "updated"
 
 MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(minutes=10)
 
+_LOGGER = logging.getLogger(__name__)
+
 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
     {
         vol.Optional(CONF_LOOP, default=True): cv.boolean,
@@ -37,35 +41,47 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
 )
 
 
-def setup_platform(hass, config, add_devices, discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
     """Set up the Environment Canada camera."""
-
     if config.get(CONF_STATION):
-        radar_object = ECRadar(
-            station_id=config[CONF_STATION], precip_type=config.get(CONF_PRECIP_TYPE)
+        lat, lon = await hass.async_add_executor_job(
+            get_station_coords, config[CONF_STATION]
         )
     else:
         lat = config.get(CONF_LATITUDE, hass.config.latitude)
         lon = config.get(CONF_LONGITUDE, hass.config.longitude)
-        radar_object = ECRadar(
-            coordinates=(lat, lon), precip_type=config.get(CONF_PRECIP_TYPE)
-        )
 
-    add_devices(
-        [ECCamera(radar_object, config.get(CONF_NAME), config[CONF_LOOP])], True
+    config[CONF_LATITUDE] = lat
+    config[CONF_LONGITUDE] = lon
+
+    trigger_import(hass, config)
+
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+    """Add a weather entity from a config_entry."""
+    radar_data = hass.data[DOMAIN][config_entry.entry_id]["radar_data"]
+
+    async_add_entities(
+        [
+            ECCamera(
+                radar_data,
+                f"{config_entry.title} Radar",
+                f"{config_entry.unique_id}-radar",
+            ),
+        ]
     )
 
 
 class ECCamera(Camera):
     """Implementation of an Environment Canada radar camera."""
 
-    def __init__(self, radar_object, camera_name, is_loop):
+    def __init__(self, radar_object, camera_name, unique_id):
         """Initialize the camera."""
         super().__init__()
 
         self.radar_object = radar_object
-        self.camera_name = camera_name
-        self.is_loop = is_loop
+        self._attr_name = camera_name
+        self._attr_unique_id = unique_id
         self.content_type = "image/gif"
         self.image = None
         self.timestamp = None
@@ -77,13 +93,6 @@ class ECCamera(Camera):
         self.update()
         return self.image
 
-    @property
-    def name(self):
-        """Return the name of the camera."""
-        if self.camera_name is not None:
-            return self.camera_name
-        return "Environment Canada Radar"
-
     @property
     def extra_state_attributes(self):
         """Return the state attributes of the device."""
@@ -92,8 +101,10 @@ class ECCamera(Camera):
     @Throttle(MIN_TIME_BETWEEN_UPDATES)
     def update(self):
         """Update radar image."""
-        if self.is_loop:
+        try:
             self.image = self.radar_object.get_loop()
-        else:
-            self.image = self.radar_object.get_latest_frame()
+        except RequestsConnectionError:
+            _LOGGER.warning("Radar data update failed due to rate limiting")
+            return
+
         self.timestamp = self.radar_object.timestamp
diff --git a/homeassistant/components/environment_canada/config_flow.py b/homeassistant/components/environment_canada/config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..c4c4835a44f07c4f4ff4a6ade25565b7963ae70d
--- /dev/null
+++ b/homeassistant/components/environment_canada/config_flow.py
@@ -0,0 +1,108 @@
+"""Config flow for Environment Canada integration."""
+from functools import partial
+import logging
+import xml.etree.ElementTree as et
+
+import aiohttp
+from env_canada import ECData
+import voluptuous as vol
+
+from homeassistant import config_entries, exceptions
+from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
+from homeassistant.helpers import config_validation as cv
+
+from .const import CONF_LANGUAGE, CONF_STATION, CONF_TITLE, DOMAIN
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def validate_input(hass, data):
+    """Validate the user input allows us to connect."""
+    lat = data.get(CONF_LATITUDE)
+    lon = data.get(CONF_LONGITUDE)
+    station = data.get(CONF_STATION)
+    lang = data.get(CONF_LANGUAGE)
+
+    weather_init = partial(
+        ECData, station_id=station, coordinates=(lat, lon), language=lang.lower()
+    )
+    weather_data = await hass.async_add_executor_job(weather_init)
+    if weather_data.metadata.get("location") is None:
+        raise TooManyAttempts
+
+    if lat is None or lon is None:
+        lat = weather_data.lat
+        lon = weather_data.lon
+
+    return {
+        CONF_TITLE: weather_data.metadata.get("location"),
+        CONF_STATION: weather_data.station_id,
+        CONF_LATITUDE: lat,
+        CONF_LONGITUDE: lon,
+    }
+
+
+class EnvironmentCanadaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
+    """Handle a config flow for Environment Canada weather."""
+
+    VERSION = 1
+
+    async def async_step_user(self, user_input=None):
+        """Handle the initial step."""
+        errors = {}
+        if user_input is not None:
+            try:
+                info = await validate_input(self.hass, user_input)
+            except TooManyAttempts:
+                errors["base"] = "too_many_attempts"
+            except et.ParseError:
+                errors["base"] = "bad_station_id"
+            except aiohttp.ClientConnectionError:
+                errors["base"] = "cannot_connect"
+            except aiohttp.ClientResponseError as err:
+                if err.status == 404:
+                    errors["base"] = "bad_station_id"
+                else:
+                    errors["base"] = "error_response"
+            except Exception:  # pylint: disable=broad-except
+                _LOGGER.exception("Unexpected exception")
+                errors["base"] = "unknown"
+
+            if not errors:
+                user_input[CONF_STATION] = info[CONF_STATION]
+                user_input[CONF_LATITUDE] = info[CONF_LATITUDE]
+                user_input[CONF_LONGITUDE] = info[CONF_LONGITUDE]
+
+                # The combination of station and language are unique for all EC weather reporting
+                await self.async_set_unique_id(
+                    f"{user_input[CONF_STATION]}-{user_input[CONF_LANGUAGE].lower()}"
+                )
+                self._abort_if_unique_id_configured()
+                return self.async_create_entry(title=info[CONF_TITLE], data=user_input)
+
+        data_schema = vol.Schema(
+            {
+                vol.Optional(CONF_STATION): str,
+                vol.Optional(
+                    CONF_LATITUDE, default=self.hass.config.latitude
+                ): cv.latitude,
+                vol.Optional(
+                    CONF_LONGITUDE, default=self.hass.config.longitude
+                ): cv.longitude,
+                vol.Required(CONF_LANGUAGE, default="English"): vol.In(
+                    ["English", "French"]
+                ),
+            }
+        )
+
+        return self.async_show_form(
+            step_id="user", data_schema=data_schema, errors=errors
+        )
+
+    async def async_step_import(self, import_data):
+        """Import entry from configuration.yaml."""
+        return await self.async_step_user(import_data)
+
+
+class TooManyAttempts(exceptions.HomeAssistantError):
+    """Error to indicate station ID is missing, invalid, or not in EC database."""
diff --git a/homeassistant/components/environment_canada/const.py b/homeassistant/components/environment_canada/const.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff32f02b21e6f50bcc8139b5d654e1672950d08b
--- /dev/null
+++ b/homeassistant/components/environment_canada/const.py
@@ -0,0 +1,9 @@
+"""Constants for EC component."""
+
+ATTR_OBSERVATION_TIME = "observation_time"
+ATTR_STATION = "station"
+CONF_ATTRIBUTION = "Data provided by Environment Canada"
+CONF_LANGUAGE = "language"
+CONF_STATION = "station"
+CONF_TITLE = "title"
+DOMAIN = "environment_canada"
diff --git a/homeassistant/components/environment_canada/manifest.json b/homeassistant/components/environment_canada/manifest.json
index 62c3e935d69a9a608ecc7e7da3592a9d3d7af508..e41c0969a87530d736efa1097cf6aadd2051b3d1 100644
--- a/homeassistant/components/environment_canada/manifest.json
+++ b/homeassistant/components/environment_canada/manifest.json
@@ -2,7 +2,8 @@
   "domain": "environment_canada",
   "name": "Environment Canada",
   "documentation": "https://www.home-assistant.io/integrations/environment_canada",
-  "requirements": ["env_canada==0.2.5"],
-  "codeowners": ["@michaeldavie"],
+  "requirements": ["env_canada==0.2.7"],
+  "codeowners": ["@gwww", "@michaeldavie"],
+  "config_flow": true,
   "iot_class": "cloud_polling"
 }
diff --git a/homeassistant/components/environment_canada/sensor.py b/homeassistant/components/environment_canada/sensor.py
index 3690703d8d25d7e2fedad9a4720be3f8111e0e9b..8e4c14832618b085d193d8eb6267a262731c2844 100644
--- a/homeassistant/components/environment_canada/sensor.py
+++ b/homeassistant/components/environment_canada/sensor.py
@@ -3,7 +3,6 @@ from datetime import datetime, timedelta
 import logging
 import re
 
-from env_canada import ECData
 import voluptuous as vol
 
 from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
@@ -17,23 +16,20 @@ from homeassistant.const import (
 )
 import homeassistant.helpers.config_validation as cv
 
-_LOGGER = logging.getLogger(__name__)
+from . import trigger_import
+from .const import ATTR_STATION, CONF_ATTRIBUTION, CONF_LANGUAGE, CONF_STATION, DOMAIN
 
 SCAN_INTERVAL = timedelta(minutes=10)
-
 ATTR_UPDATED = "updated"
-ATTR_STATION = "station"
 ATTR_TIME = "alert time"
 
-CONF_ATTRIBUTION = "Data provided by Environment Canada"
-CONF_STATION = "station"
-CONF_LANGUAGE = "language"
+_LOGGER = logging.getLogger(__name__)
 
 
 def validate_station(station):
     """Check that the station ID is well-formed."""
     if station is None:
-        return
+        return None
     if not re.fullmatch(r"[A-Z]{2}/s0000\d{3}", station):
         raise vol.error.Invalid('Station ID must be of the form "XX/s0000###"')
     return station
@@ -49,47 +45,45 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
 )
 
 
-def 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 Environment Canada sensor."""
+    trigger_import(hass, config)
 
-    if config.get(CONF_STATION):
-        ec_data = ECData(
-            station_id=config[CONF_STATION], language=config.get(CONF_LANGUAGE)
-        )
-    else:
-        lat = config.get(CONF_LATITUDE, hass.config.latitude)
-        lon = config.get(CONF_LONGITUDE, hass.config.longitude)
-        ec_data = ECData(coordinates=(lat, lon), language=config.get(CONF_LANGUAGE))
 
-    sensor_list = list(ec_data.conditions) + list(ec_data.alerts)
-    add_entities([ECSensor(sensor_type, ec_data) for sensor_type in sensor_list], True)
+async def async_setup_entry(hass, config_entry, async_add_entities):
+    """Add a weather entity from a config_entry."""
+    weather_data = hass.data[DOMAIN][config_entry.entry_id]["weather_data"]
+    sensor_list = list(weather_data.conditions) + list(weather_data.alerts)
+
+    async_add_entities(
+        [
+            ECSensor(
+                sensor_type,
+                f"{config_entry.title} {sensor_type}",
+                weather_data,
+                f"{weather_data.metadata['location']}-{sensor_type}",
+            )
+            for sensor_type in sensor_list
+        ],
+        True,
+    )
 
 
 class ECSensor(SensorEntity):
     """Implementation of an Environment Canada sensor."""
 
-    def __init__(self, sensor_type, ec_data):
+    def __init__(self, sensor_type, name, ec_data, unique_id):
         """Initialize the sensor."""
         self.sensor_type = sensor_type
         self.ec_data = ec_data
 
-        self._unique_id = None
-        self._name = None
+        self._attr_unique_id = unique_id
+        self._attr_name = name
         self._state = None
         self._attr = None
         self._unit = None
         self._device_class = None
 
-    @property
-    def unique_id(self) -> str:
-        """Return the unique ID of the sensor."""
-        return self._unique_id
-
-    @property
-    def name(self):
-        """Return the name of the sensor."""
-        return self._name
-
     @property
     def native_value(self):
         """Return the state of the sensor."""
@@ -119,9 +113,7 @@ class ECSensor(SensorEntity):
         metadata = self.ec_data.metadata
         sensor_data = conditions.get(self.sensor_type)
 
-        self._unique_id = f"{metadata['location']}-{self.sensor_type}"
         self._attr = {}
-        self._name = sensor_data.get("label")
         value = sensor_data.get("value")
 
         if isinstance(value, list):
@@ -133,7 +125,9 @@ class ECSensor(SensorEntity):
             self._state = str(value).capitalize()
         elif value is not None and len(value) > 255:
             self._state = value[:255]
-            _LOGGER.info("Value for %s truncated to 255 characters", self._unique_id)
+            _LOGGER.info(
+                "Value for %s truncated to 255 characters", self._attr_unique_id
+            )
         else:
             self._state = value
 
diff --git a/homeassistant/components/environment_canada/strings.json b/homeassistant/components/environment_canada/strings.json
new file mode 100644
index 0000000000000000000000000000000000000000..49686cba12325af0cdf95f0344cea541c3df135b
--- /dev/null
+++ b/homeassistant/components/environment_canada/strings.json
@@ -0,0 +1,23 @@
+{
+  "config": {
+    "step": {
+      "user": {
+        "title": "Environment Canada: weather location and language",
+        "description": "Either a station ID or latitude/longitude must be specified. The default latitude/longitude used are the values configured in your Home Assistant installation. The closest weather station to the coordinates will be used if specifying coordinates. If a station code is used it must follow the format: PP/code, where PP is the two-letter province and code is the station ID. The list of station IDs can be found here: https://dd.weather.gc.ca/citypage_weather/docs/site_list_towns_en.csv. Weather information can be retrieved in either English or French.",
+        "data": {
+          "latitude": "[%key:common::config_flow::data::latitude%]",
+          "longitude": "[%key:common::config_flow::data::longitude%]",
+          "station": "Weather station ID",
+          "language": "Weather information language"
+        }
+      }
+    },
+    "error": {
+      "bad_station_id": "Station ID is invalid, missing, or not found in the station ID database",
+      "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
+      "error_response": "Response from Environment Canada in error",
+      "too_many_attempts": "Connections to Environment Canada are rate limited; Try again in 60 seconds",
+      "unknown": "[%key:common::config_flow::error::unknown%]"
+    }
+  }
+}
diff --git a/homeassistant/components/environment_canada/translations/en.json b/homeassistant/components/environment_canada/translations/en.json
new file mode 100644
index 0000000000000000000000000000000000000000..94c0b947fa43f99836b98f20c6318debfa2a60a6
--- /dev/null
+++ b/homeassistant/components/environment_canada/translations/en.json
@@ -0,0 +1,23 @@
+{
+    "config": {
+        "error": {
+            "bad_station_id": "Station ID is invalid, missing, or not found in the station ID database",
+            "cannot_connect": "Failed to connect",
+            "error_response": "Response from Environment Canada in error",
+            "too_many_attempts": "Connections to Environment Canada are rate limited; Try again in 60 seconds",
+            "unknown": "Unexpected error"
+        },
+        "step": {
+            "user": {
+                "data": {
+                    "language": "Weather information language",
+                    "latitude": "Latitude",
+                    "longitude": "Longitude",
+                    "station": "Weather station ID"
+                },
+                "description": "Either a station ID or latitude/longitude must be specified. The default latitude/longitude used are the values configured in your Home Assistant installation. The closest weather station to the coordinates will be used if specifying coordinates. If a station code is used it must follow the format: PP/code, where PP is the two-letter province and code is the station ID. The list of station IDs can be found here: https://dd.weather.gc.ca/citypage_weather/docs/site_list_towns_en.csv. Weather information can be retrieved in either English or French.",
+                "title": "Environment Canada: weather location and language"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/homeassistant/components/environment_canada/weather.py b/homeassistant/components/environment_canada/weather.py
index cf24146da14ec9623912043c8aa8f101fe8218e7..281cf1174261fcce7d7fd11d6002baeaf17f0dd7 100644
--- a/homeassistant/components/environment_canada/weather.py
+++ b/homeassistant/components/environment_canada/weather.py
@@ -1,8 +1,8 @@
 """Platform for retrieving meteorological data from Environment Canada."""
 import datetime
+import logging
 import re
 
-from env_canada import ECData
 import voluptuous as vol
 
 from homeassistant.components.weather import (
@@ -30,17 +30,20 @@ from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_C
 import homeassistant.helpers.config_validation as cv
 from homeassistant.util import dt
 
+from . import trigger_import
+from .const import CONF_ATTRIBUTION, CONF_STATION, DOMAIN
+
 CONF_FORECAST = "forecast"
-CONF_ATTRIBUTION = "Data provided by Environment Canada"
-CONF_STATION = "station"
+
+_LOGGER = logging.getLogger(__name__)
 
 
 def validate_station(station):
     """Check that the station ID is well-formed."""
     if station is None:
-        return
+        return None
     if not re.fullmatch(r"[A-Z]{2}/s0000\d{3}", station):
-        raise vol.error.Invalid('Station ID must be of the form "XX/s0000###"')
+        raise vol.Invalid('Station ID must be of the form "XX/s0000###"')
     return station
 
 
@@ -72,45 +75,59 @@ ICON_CONDITION_MAP = {
 }
 
 
-def setup_platform(hass, config, add_devices, discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entries, discovery_info=None):
     """Set up the Environment Canada weather."""
-    if config.get(CONF_STATION):
-        ec_data = ECData(station_id=config[CONF_STATION])
-    else:
-        lat = config.get(CONF_LATITUDE, hass.config.latitude)
-        lon = config.get(CONF_LONGITUDE, hass.config.longitude)
-        ec_data = ECData(coordinates=(lat, lon))
+    trigger_import(hass, config)
 
-    add_devices([ECWeather(ec_data, config)])
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+    """Add a weather entity from a config_entry."""
+    weather_data = hass.data[DOMAIN][config_entry.entry_id]["weather_data"]
+
+    async_add_entities(
+        [
+            ECWeather(
+                weather_data,
+                f"{config_entry.title}",
+                config_entry.data,
+                "daily",
+                f"{config_entry.unique_id}-daily",
+            ),
+            ECWeather(
+                weather_data,
+                f"{config_entry.title} Hourly",
+                config_entry.data,
+                "hourly",
+                f"{config_entry.unique_id}-hourly",
+            ),
+        ]
+    )
 
 
 class ECWeather(WeatherEntity):
     """Representation of a weather condition."""
 
-    def __init__(self, ec_data, config):
+    def __init__(self, ec_data, name, config, forecast_type, unique_id):
         """Initialize Environment Canada weather."""
         self.ec_data = ec_data
-        self.platform_name = config.get(CONF_NAME)
-        self.forecast_type = config[CONF_FORECAST]
+        self.config = config
+        self._attr_name = name
+        self._attr_unique_id = unique_id
+        self.forecast_type = forecast_type
 
     @property
     def attribution(self):
         """Return the attribution."""
         return CONF_ATTRIBUTION
 
-    @property
-    def name(self):
-        """Return the name of the weather entity."""
-        if self.platform_name:
-            return self.platform_name
-        return self.ec_data.metadata.get("location")
-
     @property
     def temperature(self):
         """Return the temperature."""
         if self.ec_data.conditions.get("temperature", {}).get("value"):
             return float(self.ec_data.conditions["temperature"]["value"])
-        if self.ec_data.hourly_forecasts[0].get("temperature"):
+        if self.ec_data.hourly_forecasts and self.ec_data.hourly_forecasts[0].get(
+            "temperature"
+        ):
             return float(self.ec_data.hourly_forecasts[0]["temperature"])
         return None
 
@@ -161,7 +178,9 @@ class ECWeather(WeatherEntity):
 
         if self.ec_data.conditions.get("icon_code", {}).get("value"):
             icon_code = self.ec_data.conditions["icon_code"]["value"]
-        elif self.ec_data.hourly_forecasts[0].get("icon_code"):
+        elif self.ec_data.hourly_forecasts and self.ec_data.hourly_forecasts[0].get(
+            "icon_code"
+        ):
             icon_code = self.ec_data.hourly_forecasts[0]["icon_code"]
 
         if icon_code:
@@ -184,6 +203,8 @@ def get_forecast(ec_data, forecast_type):
 
     if forecast_type == "daily":
         half_days = ec_data.daily_forecasts
+        if not half_days:
+            return None
 
         today = {
             ATTR_FORECAST_TIME: dt.now().isoformat(),
diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py
index ed698fde8dc220632349021b0c4b3a74b13aaebf..cf42c2bd24fecfc8e257fa5c8c2814e262741597 100644
--- a/homeassistant/generated/config_flows.py
+++ b/homeassistant/generated/config_flows.py
@@ -77,6 +77,7 @@ FLOWS = [
     "emulated_roku",
     "enocean",
     "enphase_envoy",
+    "environment_canada",
     "epson",
     "esphome",
     "ezviz",
diff --git a/requirements_all.txt b/requirements_all.txt
index e91134d5d2f5af7459a80e1c67bbe2dc94324b9d..12b83a61bc0b9c04c78540b7783d423198f91ff4 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -591,7 +591,7 @@ enocean==0.50
 enturclient==0.2.2
 
 # homeassistant.components.environment_canada
-env_canada==0.2.5
+env_canada==0.2.7
 
 # homeassistant.components.envirophat
 # envirophat==0.0.6
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index e6e464acb7583e50bea5d23d1e387927670fd982..7341dcd518fd16cc3c4dd4c5f70e8d3e5dc94d8f 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -356,6 +356,9 @@ emulated_roku==0.2.1
 # homeassistant.components.enocean
 enocean==0.50
 
+# homeassistant.components.environment_canada
+env_canada==0.2.7
+
 # homeassistant.components.enphase_envoy
 envoy_reader==0.20.0
 
diff --git a/tests/components/environment_canada/__init__.py b/tests/components/environment_canada/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..65b0ed16207b27b92304d883114dd847a44b6684
--- /dev/null
+++ b/tests/components/environment_canada/__init__.py
@@ -0,0 +1 @@
+"""Tests for the Environment Canada integration."""
diff --git a/tests/components/environment_canada/test_config_flow.py b/tests/components/environment_canada/test_config_flow.py
new file mode 100644
index 0000000000000000000000000000000000000000..192efb05f40742e87ce4522602327184dcf47b35
--- /dev/null
+++ b/tests/components/environment_canada/test_config_flow.py
@@ -0,0 +1,178 @@
+"""Test the Environment Canada (EC) config flow."""
+from unittest.mock import MagicMock, Mock, patch
+import xml.etree.ElementTree as et
+
+import aiohttp
+import pytest
+
+from homeassistant import config_entries, data_entry_flow
+from homeassistant.components.environment_canada.const import (
+    CONF_LANGUAGE,
+    CONF_STATION,
+    DOMAIN,
+)
+from homeassistant.config_entries import SOURCE_IMPORT
+from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
+
+from tests.common import MockConfigEntry
+
+FAKE_CONFIG = {
+    CONF_STATION: "ON/s1234567",
+    CONF_LANGUAGE: "English",
+    CONF_LATITUDE: 42.42,
+    CONF_LONGITUDE: -42.42,
+}
+FAKE_TITLE = "Universal title!"
+
+
+def mocked_ec(
+    station_id=FAKE_CONFIG[CONF_STATION],
+    lat=FAKE_CONFIG[CONF_LATITUDE],
+    lon=FAKE_CONFIG[CONF_LONGITUDE],
+    lang=FAKE_CONFIG[CONF_LANGUAGE],
+    update=None,
+    metadata={"location": FAKE_TITLE},
+):
+    """Mock the env_canada library."""
+    ec_mock = MagicMock()
+    ec_mock.station_id = station_id
+    ec_mock.lat = lat
+    ec_mock.lon = lon
+    ec_mock.language = lang
+    ec_mock.metadata = metadata
+
+    if update:
+        ec_mock.update = update
+    else:
+        ec_mock.update = Mock()
+
+    return patch(
+        "homeassistant.components.environment_canada.config_flow.ECData",
+        return_value=ec_mock,
+    )
+
+
+async def test_create_entry(hass):
+    """Test creating an entry."""
+    with mocked_ec(), patch(
+        "homeassistant.components.environment_canada.async_setup_entry",
+        return_value=True,
+    ):
+        flow = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": config_entries.SOURCE_USER}
+        )
+        result = await hass.config_entries.flow.async_configure(
+            flow["flow_id"], FAKE_CONFIG
+        )
+        await hass.async_block_till_done()
+        assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+        assert result["data"] == FAKE_CONFIG
+        assert result["title"] == FAKE_TITLE
+
+
+async def test_create_same_entry_twice(hass):
+    """Test duplicate entries."""
+    entry = MockConfigEntry(
+        domain=DOMAIN,
+        data=FAKE_CONFIG,
+        unique_id="ON/s1234567-english",
+    )
+    entry.add_to_hass(hass)
+
+    with mocked_ec(), patch(
+        "homeassistant.components.environment_canada.async_setup_entry",
+        return_value=True,
+    ):
+        flow = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": config_entries.SOURCE_USER}
+        )
+        result = await hass.config_entries.flow.async_configure(
+            flow["flow_id"], FAKE_CONFIG
+        )
+        await hass.async_block_till_done()
+        assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
+        assert result["reason"] == "already_configured"
+
+
+async def test_too_many_attempts(hass):
+    """Test hitting rate limit."""
+    with mocked_ec(metadata={}), patch(
+        "homeassistant.components.environment_canada.async_setup_entry",
+        return_value=True,
+    ):
+        flow = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": config_entries.SOURCE_USER}
+        )
+        result = await hass.config_entries.flow.async_configure(
+            flow["flow_id"],
+            {},
+        )
+        await hass.async_block_till_done()
+        assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
+        assert result["errors"] == {"base": "too_many_attempts"}
+
+
+@pytest.mark.parametrize(
+    "error",
+    [
+        (aiohttp.ClientResponseError(Mock(), (), status=404), "bad_station_id"),
+        (aiohttp.ClientResponseError(Mock(), (), status=400), "error_response"),
+        (aiohttp.ClientConnectionError, "cannot_connect"),
+        (et.ParseError, "bad_station_id"),
+        (ValueError, "unknown"),
+    ],
+)
+async def test_exception_handling(hass, error):
+    """Test exception handling."""
+    exc, base_error = error
+    with patch(
+        "homeassistant.components.environment_canada.config_flow.ECData",
+        side_effect=exc,
+    ):
+        flow = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": config_entries.SOURCE_USER}
+        )
+        result = await hass.config_entries.flow.async_configure(
+            flow["flow_id"],
+            {},
+        )
+        await hass.async_block_till_done()
+        assert result["type"] == "form"
+        assert result["errors"] == {"base": base_error}
+
+
+async def test_lat_or_lon_not_specified(hass):
+    """Test that the import step works."""
+    with mocked_ec(), patch(
+        "homeassistant.components.environment_canada.async_setup_entry",
+        return_value=True,
+    ):
+        fake_config = dict(FAKE_CONFIG)
+        del fake_config[CONF_LATITUDE]
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": SOURCE_IMPORT}, data=fake_config
+        )
+        await hass.async_block_till_done()
+        assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+        assert result["data"] == FAKE_CONFIG
+        assert result["title"] == FAKE_TITLE
+
+
+async def test_async_step_import(hass):
+    """Test that the import step works."""
+    with mocked_ec(), patch(
+        "homeassistant.components.environment_canada.async_setup_entry",
+        return_value=True,
+    ):
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": SOURCE_IMPORT}, data=FAKE_CONFIG
+        )
+        await hass.async_block_till_done()
+        assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+        assert result["data"] == FAKE_CONFIG
+        assert result["title"] == FAKE_TITLE
+
+        result = await hass.config_entries.flow.async_init(
+            DOMAIN, context={"source": SOURCE_IMPORT}, data=FAKE_CONFIG
+        )
+        assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT