diff --git a/requirements_test.txt b/requirements_test.txt
index 600916615bee2cb67199825f4629f7a48611e130..86b8b496e8339988730ca864771791ed06f7c967 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -12,6 +12,7 @@ mypy==0.780
 pre-commit==2.7.1
 pylint==2.6.0
 astroid==2.4.2
+pipdeptree==1.0.0
 pylint-strict-informational==0.1
 pytest-aiohttp==0.3.0
 pytest-cov==2.10.0
@@ -22,3 +23,5 @@ pytest-xdist==1.32.0
 pytest==5.4.3
 requests_mock==1.8.0
 responses==0.10.6
+stdlib-list==0.7.0
+tqdm==4.48.2
diff --git a/script/hassfest/__main__.py b/script/hassfest/__main__.py
index 6fbefc11a2f0504d39bc1337ff63b50cac26976a..26af118d11e6bec9cba2c863920349dad641832f 100644
--- a/script/hassfest/__main__.py
+++ b/script/hassfest/__main__.py
@@ -11,6 +11,7 @@ from . import (
     dependencies,
     json,
     manifest,
+    requirements,
     services,
     ssdp,
     translations,
@@ -55,6 +56,11 @@ def get_config() -> Config:
         type=valid_integration_path,
         help="Validate a single integration",
     )
+    parser.add_argument(
+        "--requirements",
+        action="store_true",
+        help="Validate requirements",
+    )
     parsed = parser.parse_args()
 
     if parsed.action is None:
@@ -75,6 +81,7 @@ def get_config() -> Config:
         root=pathlib.Path(".").absolute(),
         specific_integrations=parsed.integration_path,
         action=parsed.action,
+        requirements=parsed.requirements,
     )
 
 
@@ -86,7 +93,10 @@ def main():
         print(err)
         return 1
 
-    plugins = INTEGRATION_PLUGINS
+    plugins = [*INTEGRATION_PLUGINS]
+
+    if config.requirements:
+        plugins.append(requirements)
 
     if config.specific_integrations:
         integrations = {}
@@ -104,6 +114,8 @@ def main():
         try:
             start = monotonic()
             print(f"Validating {plugin.__name__.split('.')[-1]}...", end="", flush=True)
+            if plugin is requirements:
+                print()
             plugin.validate(integrations, config)
             print(" done in {:.2f}s".format(monotonic() - start))
         except RuntimeError as err:
diff --git a/script/hassfest/model.py b/script/hassfest/model.py
index c993689aaab4d54b530e38d1ea258fa3329a376f..8c55c2818f1d474fc31f7fb165857693dcba8998 100644
--- a/script/hassfest/model.py
+++ b/script/hassfest/model.py
@@ -27,6 +27,7 @@ class Config:
     specific_integrations: Optional[pathlib.Path] = attr.ib()
     root: pathlib.Path = attr.ib()
     action: str = attr.ib()
+    requirements: bool = attr.ib()
     errors: List[Error] = attr.ib(factory=list)
     cache: Dict[str, Any] = attr.ib(factory=dict)
 
diff --git a/script/hassfest/requirements.py b/script/hassfest/requirements.py
new file mode 100644
index 0000000000000000000000000000000000000000..ab43cd62bd5e730a779be0d7cc4be192cbe23219
--- /dev/null
+++ b/script/hassfest/requirements.py
@@ -0,0 +1,173 @@
+"""Validate requirements."""
+import operator
+import re
+import subprocess
+import sys
+from typing import Dict, Set
+
+from stdlib_list import stdlib_list
+from tqdm import tqdm
+
+from homeassistant.const import REQUIRED_PYTHON_VER
+import homeassistant.util.package as pkg_util
+from script.gen_requirements_all import COMMENT_REQUIREMENTS
+
+from .model import Config, Integration
+
+IGNORE_PACKAGES = {
+    commented.lower().replace("_", "-") for commented in COMMENT_REQUIREMENTS
+}
+PACKAGE_REGEX = re.compile(r"^(?:--.+\s)?([-_\.\w\d]+).*==.+$")
+PIP_REGEX = re.compile(r"^(--.+\s)?([-_\.\w\d]+.*(?:==|>=|<=|~=|!=|<|>|===)?.*$)")
+SUPPORTED_PYTHON_TUPLES = [
+    REQUIRED_PYTHON_VER[:2],
+    tuple(map(operator.add, REQUIRED_PYTHON_VER, (0, 1, 0)))[:2],
+]
+SUPPORTED_PYTHON_VERSIONS = [
+    ".".join(map(str, version_tuple)) for version_tuple in SUPPORTED_PYTHON_TUPLES
+]
+STD_LIBS = {version: set(stdlib_list(version)) for version in SUPPORTED_PYTHON_VERSIONS}
+
+
+def normalize_package_name(requirement: str) -> str:
+    """Return a normalized package name from a requirement string."""
+    match = PACKAGE_REGEX.search(requirement)
+    if not match:
+        return ""
+
+    # pipdeptree needs lowercase and dash instead of underscore as separator
+    package = match.group(1).lower().replace("_", "-")
+
+    return package
+
+
+def validate(integrations: Dict[str, Integration], config: Config):
+    """Handle requirements for integrations."""
+    # check for incompatible requirements
+    for integration in tqdm(integrations.values()):
+        if not integration.manifest:
+            continue
+
+        validate_requirements(integration)
+
+
+def validate_requirements(integration: Integration):
+    """Validate requirements."""
+    integration_requirements = set()
+    integration_packages = set()
+    for req in integration.requirements:
+        package = normalize_package_name(req)
+        if not package:
+            integration.add_error(
+                "requirements",
+                f"Failed to normalize package name from requirement {req}",
+            )
+            return
+        if package in IGNORE_PACKAGES:
+            continue
+        integration_requirements.add(req)
+        integration_packages.add(package)
+
+    install_ok = install_requirements(integration, integration_requirements)
+
+    if not install_ok:
+        return
+
+    all_integration_requirements = get_requirements(integration, integration_packages)
+
+    if integration_requirements and not all_integration_requirements:
+        integration.add_error(
+            "requirements",
+            f"Failed to resolve requirements {integration_requirements}",
+        )
+        return
+
+    # Check for requirements incompatible with standard library.
+    for version, std_libs in STD_LIBS.items():
+        for req in all_integration_requirements:
+            if req in std_libs:
+                integration.add_error(
+                    "requirements",
+                    f"Package {req} is not compatible with Python {version} standard library",
+                )
+
+
+def get_requirements(integration: Integration, packages: Set[str]) -> Set[str]:
+    """Return all (recursively) requirements for an integration."""
+    all_requirements = set()
+
+    for package in packages:
+        try:
+            result = subprocess.run(
+                ["pipdeptree", "-w", "silence", "--packages", package],
+                check=True,
+                capture_output=True,
+                text=True,
+            )
+        except subprocess.SubprocessError:
+            integration.add_error(
+                "requirements", f"Failed to resolve requirements for {package}"
+            )
+            continue
+
+        # parse output to get a set of package names
+        output = result.stdout
+        lines = output.split("\n")
+        parent = lines[0].split("==")[0]  # the first line is the parent package
+        if parent:
+            all_requirements.add(parent)
+
+        for line in lines[1:]:  # skip the first line which we already processed
+            line = line.strip()
+            line = line.lstrip("- ")
+            package = line.split("[")[0]
+            package = package.strip()
+            if not package:
+                continue
+            all_requirements.add(package)
+
+    return all_requirements
+
+
+def install_requirements(integration: Integration, requirements: Set[str]) -> bool:
+    """Install integration requirements.
+
+    Return True if successful.
+    """
+    for req in requirements:
+        try:
+            is_installed = pkg_util.is_installed(req)
+        except ValueError:
+            is_installed = False
+
+        if is_installed:
+            continue
+
+        match = PIP_REGEX.search(req)
+
+        if not match:
+            integration.add_error(
+                "requirements",
+                f"Failed to parse requirement {req} before installation",
+            )
+            continue
+
+        install_args = match.group(1)
+        requirement_arg = match.group(2)
+
+        args = [sys.executable, "-m", "pip", "install", "--quiet"]
+        if install_args:
+            args.append(install_args)
+        args.append(requirement_arg)
+        try:
+            subprocess.run(args, check=True)
+        except subprocess.SubprocessError:
+            integration.add_error(
+                "requirements",
+                f"Requirement {req} failed to install",
+            )
+
+    if integration.errors:
+        return False
+
+    return True