diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..eb2fb5039b75d62a865abd5f0fab10cfed4f51a1
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,751 @@
+name: CI
+
+# yamllint disable-line rule:truthy
+on:
+  push:
+    branches:
+      - dev
+      - rc
+      - master
+  pull_request: ~
+
+env:
+  DEFAULT_PYTHON: 3.7
+  PRE_COMMIT_HOME: ~/.cache/pre-commit
+
+jobs:
+  # Separate job to pre-populate the base dependency cache
+  # This prevent upcoming jobs to do the same individually
+  prepare-base:
+    name: Prepare base dependencies
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
+        id: python
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ env.DEFAULT_PYTHON }}
+      - name: Restore base Python virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version
+            }}-${{ hashFiles('requirements.txt') }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+          restore-keys: |
+            ${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements_test.txt') }}-
+            ${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements.txt') }}
+            ${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}-
+            ${{ runner.os }}-base-venv-
+      - name: Create Python virtual environment
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          python -m venv venv
+          . venv/bin/activate
+          pip install -U pip setuptools
+          pip install -r requirements.txt -r requirements_test.txt -c homeassistant/package_constraints.txt
+          # Uninstalling typing as a workaround. Eventually we should make sure
+          # all our dependencies drop typing.
+          # Find offending deps with `pipdeptree -r -p typing`
+          pip uninstall -y typing
+      - name: Restore pre-commit environment from cache
+        id: cache-precommit
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.PRE_COMMIT_HOME }}
+          key: |
+            ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
+          restore-keys: |
+            ${{ runner.os }}-pre-commit-
+      - name: Install pre-commit dependencies
+        if: steps.cache-precommit.outputs.cache-hit != 'true'
+        run: |
+          . venv/bin/activate
+          pre-commit install-hooks
+
+  lint-bandit:
+    name: Check bandit
+    runs-on: ubuntu-latest
+    needs: prepare-base
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
+        uses: actions/setup-python@v2
+        id: python
+        with:
+          python-version: ${{ env.DEFAULT_PYTHON }}
+      - name: Restore base Python virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version
+            }}-${{ hashFiles('requirements.txt') }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+      - name: Fail job if Python cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Restore pre-commit environment from cache
+        id: cache-precommit
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.PRE_COMMIT_HOME }}
+          key: |
+            ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
+      - name: Fail job if cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Run bandit
+        run: |
+          . venv/bin/activate
+          pre-commit run --hook-stage manual bandit --all-files
+
+  lint-black:
+    name: Check black
+    runs-on: ubuntu-latest
+    needs: prepare-base
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
+        uses: actions/setup-python@v2
+        id: python
+        with:
+          python-version: ${{ env.DEFAULT_PYTHON }}
+      - name: Restore base Python virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version
+            }}-${{ hashFiles('requirements.txt') }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+      - name: Fail job if Python cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Restore pre-commit environment from cache
+        id: cache-precommit
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.PRE_COMMIT_HOME }}
+          key: |
+            ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
+      - name: Fail job if cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Run black
+        run: |
+          . venv/bin/activate
+          pre-commit run --hook-stage manual black --all-files
+
+  lint-codespell:
+    name: Check codespell
+    runs-on: ubuntu-latest
+    needs: prepare-base
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
+        uses: actions/setup-python@v2
+        id: python
+        with:
+          python-version: ${{ env.DEFAULT_PYTHON }}
+      - name: Restore base Python virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version
+            }}-${{ hashFiles('requirements.txt') }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+      - name: Fail job if Python cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Restore pre-commit environment from cache
+        id: cache-precommit
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.PRE_COMMIT_HOME }}
+          key: |
+            ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
+      - name: Fail job if cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Run codespell
+        run: |
+          . venv/bin/activate
+          pre-commit run --show-diff-on-failure --color=always --hook-stage manual codespell --all-files
+
+  lint-dockerfile:
+    name: Check Dockerfile
+    runs-on: ubuntu-latest
+    needs: prepare-base
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name: Check Dockerfile
+        uses: docker://hadolint/hadolint:v1.18.0
+        with:
+          args: hadolint Dockerfile
+      - name: Check Dockerfile.dev
+        uses: docker://hadolint/hadolint:v1.18.0
+        with:
+          args: hadolint Dockerfile.dev
+
+  lint-executable-shebangs:
+    name: Check executables
+    runs-on: ubuntu-latest
+    needs: prepare-base
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
+        uses: actions/setup-python@v2
+        id: python
+        with:
+          python-version: ${{ env.DEFAULT_PYTHON }}
+      - name: Restore base Python virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version
+            }}-${{ hashFiles('requirements.txt') }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+      - name: Fail job if Python cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Restore pre-commit environment from cache
+        id: cache-precommit
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.PRE_COMMIT_HOME }}
+          key: |
+            ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
+      - name: Fail job if cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Run executables check
+        run: |
+          . venv/bin/activate
+          pre-commit run --hook-stage manual check-executables-have-shebangs --all-files
+
+  lint-flake8:
+    name: Check flake8
+    runs-on: ubuntu-latest
+    needs: prepare-base
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
+        uses: actions/setup-python@v2
+        id: python
+        with:
+          python-version: ${{ env.DEFAULT_PYTHON }}
+      - name: Restore base Python virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version
+            }}-${{ hashFiles('requirements.txt') }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+      - name: Fail job if Python cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Restore pre-commit environment from cache
+        id: cache-precommit
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.PRE_COMMIT_HOME }}
+          key: |
+            ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
+      - name: Fail job if cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Run flake8
+        run: |
+          . venv/bin/activate
+          pre-commit run --hook-stage manual flake8 --all-files
+
+  lint-isort:
+    name: Check isort
+    runs-on: ubuntu-latest
+    needs: prepare-base
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
+        uses: actions/setup-python@v2
+        id: python
+        with:
+          python-version: ${{ env.DEFAULT_PYTHON }}
+      - name: Restore base Python virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version
+            }}-${{ hashFiles('requirements.txt') }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+      - name: Fail job if Python cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Restore pre-commit environment from cache
+        id: cache-precommit
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.PRE_COMMIT_HOME }}
+          key: |
+            ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
+      - name: Fail job if cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Run isort
+        run: |
+          . venv/bin/activate
+          pre-commit run --hook-stage manual isort --all-files
+
+  lint-json:
+    name: Check JSON
+    runs-on: ubuntu-latest
+    needs: prepare-base
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
+        uses: actions/setup-python@v2
+        id: python
+        with:
+          python-version: ${{ env.DEFAULT_PYTHON }}
+      - name: Restore base Python virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version
+            }}-${{ hashFiles('requirements.txt') }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+      - name: Fail job if Python cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Restore pre-commit environment from cache
+        id: cache-precommit
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.PRE_COMMIT_HOME }}
+          key: |
+            ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
+      - name: Fail job if cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Run check-json
+        run: |
+          . venv/bin/activate
+          pre-commit run --hook-stage manual check-json --all-files
+
+  lint-pyupgrade:
+    name: Check pyupgrade
+    runs-on: ubuntu-latest
+    needs: prepare-base
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
+        uses: actions/setup-python@v2
+        id: python
+        with:
+          python-version: ${{ env.DEFAULT_PYTHON }}
+      - name: Restore base Python virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version
+            }}-${{ hashFiles('requirements.txt') }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+      - name: Fail job if Python cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Restore pre-commit environment from cache
+        id: cache-precommit
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.PRE_COMMIT_HOME }}
+          key: |
+            ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
+      - name: Fail job if cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Run pyupgrade
+        run: |
+          . venv/bin/activate
+          pre-commit run --hook-stage manual pyupgrade --all-files
+
+  # Disabled until we have the existing issues fixed
+  # lint-shellcheck:
+  #   name: Check ShellCheck
+  #   runs-on: ubuntu-latest
+  #   needs: prepare-base
+  #   steps:
+  #     - name: Check out code from GitHub
+  #       uses: actions/checkout@v2
+  #     - name: Run ShellCheck
+  #       uses: ludeeus/action-shellcheck@0.3.0
+
+  lint-yaml:
+    name: Check YAML
+    runs-on: ubuntu-latest
+    needs: prepare-base
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
+        uses: actions/setup-python@v2
+        id: python
+        with:
+          python-version: ${{ env.DEFAULT_PYTHON }}
+      - name: Restore base Python virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version
+            }}-${{ hashFiles('requirements.txt') }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+      - name: Fail job if Python cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Restore pre-commit environment from cache
+        id: cache-precommit
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.PRE_COMMIT_HOME }}
+          key: |
+            ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
+      - name: Fail job if cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Run yamllint
+        run: |
+          . venv/bin/activate
+          pre-commit run --hook-stage manual yamllint --all-files
+
+  hassfest:
+    name: Check hassfest
+    runs-on: ubuntu-latest
+    needs: prepare-base
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
+        uses: actions/setup-python@v2
+        id: python
+        with:
+          python-version: ${{ env.DEFAULT_PYTHON }}
+      - name: Restore base Python virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version
+            }}-${{ hashFiles('requirements.txt') }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+      - name: Fail job if Python cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Run hassfest
+        run: |
+          . venv/bin/activate
+          python -m script.hassfest --action validate
+
+  gen-requirements-all:
+    name: Check all requirements
+    runs-on: ubuntu-latest
+    needs: prepare-base
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
+        uses: actions/setup-python@v2
+        id: python
+        with:
+          python-version: ${{ env.DEFAULT_PYTHON }}
+      - name: Restore base Python virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version
+            }}-${{ hashFiles('requirements.txt') }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+      - name: Fail job if Python cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Run gen_requirements_all.py
+        run: |
+          . venv/bin/activate
+          python -m script.gen_requirements_all validate
+
+  prepare-tests:
+    name: Prepare tests for Python ${{ matrix.python-version }}
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [3.7, 3.8]
+    container: homeassistant/ci-azure:${{ matrix.python-version }}
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name:
+          Restore full Python ${{ matrix.python-version }} virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-venv-${{ matrix.python-version }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('requirements_all.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+          restore-keys: |
+            ${{ runner.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('requirements_test.txt') }}-${{ hashFiles('requirements_all.txt') }}
+            ${{ runner.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('requirements_test.txt') }}
+            ${{ runner.os }}-venv-${{ matrix.python-version }}-
+      - name:
+          Create full Python ${{ matrix.python-version }} virtual environment
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          python -m venv venv
+          . venv/bin/activate
+          pip install -U pip setuptools wheel
+          pip install -r requirements_all.txt -c homeassistant/package_constraints.txt
+          pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
+          # Uninstalling typing as a workaround. Eventually we should make sure
+          # all our dependencies drop typing.
+          # Find offending deps with `pipdeptree -r -p typing`
+          pip uninstall -y typing
+          pip install -e .
+
+  pylint:
+    name: Check pylint
+    runs-on: ubuntu-latest
+    needs: prepare-tests
+    strategy:
+      matrix:
+        python-version: [3.7]
+    container: homeassistant/ci-azure:${{ matrix.python-version }}
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name:
+          Restore full Python ${{ matrix.python-version }} virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-venv-${{ matrix.python-version }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('requirements_all.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+      - name: Fail job if Python cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Run pylint
+        run: |
+          . venv/bin/activate
+          pylint homeassistant
+
+  mypy:
+    name: Check mypy
+    runs-on: ubuntu-latest
+    needs: prepare-tests
+    strategy:
+      matrix:
+        python-version: [3.7]
+    container: homeassistant/ci-azure:${{ matrix.python-version }}
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name:
+          Restore full Python ${{ matrix.python-version }} virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-venv-${{ matrix.python-version }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('requirements_all.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+      - name: Fail job if Python cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Run mypy
+        run: |
+          . venv/bin/activate
+          mypy homeassistant
+
+  pytest:
+    runs-on: ubuntu-latest
+    needs: prepare-tests
+    strategy:
+      matrix:
+        group: [1, 2, 3, 4]
+        python-version: [3.7, 3.8]
+    name: >-
+      Run tests Python ${{ matrix.python-version }} (group ${{ matrix.group }})
+    container: homeassistant/ci-azure:${{ matrix.python-version }}
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name:
+          Restore full Python ${{ matrix.python-version }} virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-venv-${{ matrix.python-version }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('requirements_all.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+      - name: Fail job if Python cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Run pytest
+        run: |
+          . venv/bin/activate
+          pytest \
+            -qq \
+            --timeout=9 \
+            --durations=10 \
+            -n auto \
+            --dist=loadfile \
+            --test-group-count 4 \
+            --test-group=${{ matrix.group }} \
+            --cov homeassistant \
+            -o console_output_style=count \
+            -p no:sugar \
+            tests
+      - name: Upload coverage artifact
+        uses: actions/upload-artifact@v1
+        with:
+          name: coverage-${{ matrix.python-version }}-group${{ matrix.group }}
+          path: .coverage
+      - name: Check dirty
+        run: |
+          ./script/check_dirty
+
+  coverage:
+    name: Process test coverage
+    runs-on: ubuntu-latest
+    needs: pytest
+    strategy:
+      matrix:
+        python-version: [3.7]
+    container: homeassistant/ci-azure:${{ matrix.python-version }}
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v2
+      - name:
+          Restore full Python ${{ matrix.python-version }} virtual environment
+        id: cache-venv
+        uses: actions/cache@v2
+        with:
+          path: venv
+          key: >-
+            ${{ runner.os }}-venv-${{ matrix.python-version }}-${{
+            hashFiles('requirements_test.txt') }}-${{
+            hashFiles('requirements_all.txt') }}-${{
+            hashFiles('homeassistant/package_constraints.txt') }}
+      - name: Fail job if Python cache restore failed
+        if: steps.cache-venv.outputs.cache-hit != 'true'
+        run: |
+          echo "Failed to restore Python virtual environment from cache"
+          exit 1
+      - name: Download all coverage artifacts
+        uses: actions/download-artifact@v2
+      - name: Combine coverage results
+        run: |
+          . venv/bin/activate
+          coverage combine coverage*/.coverage*
+          coverage report --fail-under=94
+          coverage xml
+      - name: Upload coverage to Codecov
+        uses: codecov/codecov-action@v1
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d281c0f870a481aaccb11d254d5a202cde9597e9
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,20 @@
+# Home Assistant Core
+aiohttp==3.6.1
+astral==1.10.1
+async_timeout==3.0.1
+attrs==19.3.0
+bcrypt==3.1.7
+certifi>=2020.4.5.1
+ciso8601==2.1.3
+importlib-metadata==1.6.0;python_version<'3.8'
+jinja2>=2.11.1
+PyJWT==1.7.1
+cryptography==2.9.2
+pip>=8.0.3
+python-slugify==4.0.0
+pytz>=2020.1
+pyyaml==5.3.1
+requests==2.24.0
+ruamel.yaml==0.15.100
+voluptuous==0.11.7
+voluptuous-serialize==2.3.0
diff --git a/requirements_all.txt b/requirements_all.txt
index 36b6685e8806ff9d4141f050e8a090d6b304faf9..f2e063c4ea8088e8d5673347d16a0b73b0d6d491 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -1,23 +1,5 @@
-# Home Assistant core
-aiohttp==3.6.1
-astral==1.10.1
-async_timeout==3.0.1
-attrs==19.3.0
-bcrypt==3.1.7
-certifi>=2020.4.5.1
-ciso8601==2.1.3
-importlib-metadata==1.6.0;python_version<'3.8'
-jinja2>=2.11.1
-PyJWT==1.7.1
-cryptography==2.9.2
-pip>=8.0.3
-python-slugify==4.0.0
-pytz>=2020.1
-pyyaml==5.3.1
-requests==2.24.0
-ruamel.yaml==0.15.100
-voluptuous==0.11.7
-voluptuous-serialize==2.3.0
+# Home Assistant Core, full dependency set
+-r requirements.txt
 
 # homeassistant.components.nuimo_controller
 --only-binary=all nuimo==0.1.0
diff --git a/requirements_test.txt b/requirements_test.txt
index e26ac56506d2ab188acf960f300650703c88bb28..2e2e3f213ba5d575e23fd6d5cd3bb473c23df996 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -14,8 +14,10 @@ astroid==2.3.3
 pylint-strict-informational==0.1
 pytest-aiohttp==0.3.0
 pytest-cov==2.10.0
+pytest-test-groups==1.0.3
 pytest-sugar==0.9.3
 pytest-timeout==1.3.4
+pytest-xdist==1.32.0
 pytest==5.4.3
 requests_mock==1.8.0
-responses==0.10.6
+responses==0.10.6
\ No newline at end of file
diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py
index c3b7b05dc309497c8e09d5e42f2cb5d9a32978a6..f8b6714f54b1fc404a25bdcb501f1960be37f586 100755
--- a/script/gen_requirements_all.py
+++ b/script/gen_requirements_all.py
@@ -222,13 +222,22 @@ def generate_requirements_list(reqs):
     return "".join(output)
 
 
-def requirements_all_output(reqs):
-    """Generate output for requirements_all."""
+def requirements_output(reqs):
+    """Generate output for requirements."""
     output = []
-    output.append("# Home Assistant core")
+    output.append("# Home Assistant Core")
     output.append("\n")
     output.append("\n".join(core_requirements()))
     output.append("\n")
+
+    return "".join(output)
+
+
+def requirements_all_output(reqs):
+    """Generate output for requirements_all."""
+    output = []
+    output.append("# Home Assistant Core, full dependency set\n")
+    output.append("-r requirements.txt\n")
     output.append(generate_requirements_list(reqs))
 
     return "".join(output)
@@ -315,13 +324,15 @@ def main(validate):
     if data is None:
         return 1
 
-    reqs_file = requirements_all_output(data)
+    reqs_file = requirements_output(data)
+    reqs_all_file = requirements_all_output(data)
     reqs_test_file = requirements_test_output(data)
     reqs_pre_commit_file = requirements_pre_commit_output()
     constraints = gather_constraints()
 
     files = (
-        ("requirements_all.txt", reqs_file),
+        ("requirements.txt", reqs_file),
+        ("requirements_all.txt", reqs_all_file),
         ("requirements_test_pre_commit.txt", reqs_pre_commit_file),
         ("requirements_test_all.txt", reqs_test_file),
         ("homeassistant/package_constraints.txt", constraints),