From 98a007cb2f04eaba6bc28651d964816f45e57724 Mon Sep 17 00:00:00 2001
From: TimL <tl@smlight.tech>
Date: Tue, 20 Aug 2024 18:44:06 +1000
Subject: [PATCH] New Integration: SMLIGHT SLZB-06 Adapters Integration
 (#118675)

* Initial SMLIGHT integration

Signed-off-by: Tim Lunn <tl@smlight.tech>

* Generated content

Signed-off-by: Tim Lunn <tl@smlight.tech>

* Cleanup LOGGING

* Use runtime data

* Call super first

* coordinator instance attributes

* Move coordinatorEntity and attr to base class

* cleanup sensors

* update strings to use sentence case

* Improve reauth flow on incorrect credentials

* Use fixture for config_flow tests and test to completion

* Split uptime hndling into a new uptime sensor entity

* Drop server side events and internet callback

will bring this back with binary sensor Platform

* consolidate coordinator setup

* entity always include connections

* get_hostname tweak

* Add tests for init, coordinator and sensor

* Use custom type SmConfigEntry

* update sensor snapshot

* Drop reauth flow for later PR

* Use _async_setup for initial setup

* drop internet to be set later

* sensor fixes

* config flow re

* typing fixes

* Bump pysmlight dependency to 0.0.12

* dont trigger invalid auth message when first loading auth step

* Merge uptime sensors back into main sensor class

* clarify uptime handling

* Apply suggestions from code review

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* address review comments

* pass host as parameter to the dataCoordinator

* drop uptime sensors for a later PR

* update sensor test snapshot

* move coordinator unique_id to _async_setup

* fix CI

* Apply suggestions from code review

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* drop invalid_auth test tag

* use snapshot_platform, update fixtures

* Finish all tests with abort or create entry

* drop coordinator tests and remove hostname support

* add test for update failure on connection error

* use freezer for update_failed test

* fix pysmlight imports

---------

Signed-off-by: Tim Lunn <tl@smlight.tech>
Co-authored-by: Tim Lunn <tim@feathertop.org>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
---
 CODEOWNERS                                    |   2 +
 homeassistant/components/smlight/__init__.py  |  30 +
 .../components/smlight/config_flow.py         | 151 ++++
 homeassistant/components/smlight/const.py     |  11 +
 .../components/smlight/coordinator.py         |  71 ++
 homeassistant/components/smlight/entity.py    |  31 +
 .../components/smlight/manifest.json          |  15 +
 homeassistant/components/smlight/sensor.py    | 103 +++
 homeassistant/components/smlight/strings.json |  49 ++
 homeassistant/generated/config_flows.py       |   1 +
 homeassistant/generated/integrations.json     |   6 +
 homeassistant/generated/zeroconf.py           |   3 +
 requirements_all.txt                          |   3 +
 requirements_test_all.txt                     |   3 +
 tests/components/smlight/__init__.py          |   1 +
 tests/components/smlight/conftest.py          |  74 ++
 tests/components/smlight/fixtures/info.json   |  16 +
 .../components/smlight/fixtures/sensors.json  |  14 +
 .../smlight/snapshots/test_init.ambr          |  33 +
 .../smlight/snapshots/test_sensor.ambr        | 741 ++++++++++++++++++
 tests/components/smlight/test_config_flow.py  | 365 +++++++++
 tests/components/smlight/test_init.py         |  94 +++
 tests/components/smlight/test_sensor.py       |  54 ++
 23 files changed, 1871 insertions(+)
 create mode 100644 homeassistant/components/smlight/__init__.py
 create mode 100644 homeassistant/components/smlight/config_flow.py
 create mode 100644 homeassistant/components/smlight/const.py
 create mode 100644 homeassistant/components/smlight/coordinator.py
 create mode 100644 homeassistant/components/smlight/entity.py
 create mode 100644 homeassistant/components/smlight/manifest.json
 create mode 100644 homeassistant/components/smlight/sensor.py
 create mode 100644 homeassistant/components/smlight/strings.json
 create mode 100644 tests/components/smlight/__init__.py
 create mode 100644 tests/components/smlight/conftest.py
 create mode 100644 tests/components/smlight/fixtures/info.json
 create mode 100644 tests/components/smlight/fixtures/sensors.json
 create mode 100644 tests/components/smlight/snapshots/test_init.ambr
 create mode 100644 tests/components/smlight/snapshots/test_sensor.ambr
 create mode 100644 tests/components/smlight/test_config_flow.py
 create mode 100644 tests/components/smlight/test_init.py
 create mode 100644 tests/components/smlight/test_sensor.py

diff --git a/CODEOWNERS b/CODEOWNERS
index 367c6eee2bf..1618b18a8be 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1329,6 +1329,8 @@ build.json @home-assistant/supervisor
 /homeassistant/components/smarty/ @z0mbieprocess
 /homeassistant/components/smhi/ @gjohansson-ST
 /tests/components/smhi/ @gjohansson-ST
+/homeassistant/components/smlight/ @tl-sl
+/tests/components/smlight/ @tl-sl
 /homeassistant/components/sms/ @ocalvo
 /tests/components/sms/ @ocalvo
 /homeassistant/components/snapcast/ @luar123
diff --git a/homeassistant/components/smlight/__init__.py b/homeassistant/components/smlight/__init__.py
new file mode 100644
index 00000000000..16eb60b9c87
--- /dev/null
+++ b/homeassistant/components/smlight/__init__.py
@@ -0,0 +1,30 @@
+"""SMLIGHT SLZB Zigbee device integration."""
+
+from __future__ import annotations
+
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import CONF_HOST, Platform
+from homeassistant.core import HomeAssistant
+
+from .coordinator import SmDataUpdateCoordinator
+
+PLATFORMS: list[Platform] = [
+    Platform.SENSOR,
+]
+type SmConfigEntry = ConfigEntry[SmDataUpdateCoordinator]
+
+
+async def async_setup_entry(hass: HomeAssistant, entry: SmConfigEntry) -> bool:
+    """Set up SMLIGHT Zigbee from a config entry."""
+    coordinator = SmDataUpdateCoordinator(hass, entry.data[CONF_HOST])
+    await coordinator.async_config_entry_first_refresh()
+    entry.runtime_data = coordinator
+
+    await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
+
+    return True
+
+
+async def async_unload_entry(hass: HomeAssistant, entry: SmConfigEntry) -> bool:
+    """Unload a config entry."""
+    return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
diff --git a/homeassistant/components/smlight/config_flow.py b/homeassistant/components/smlight/config_flow.py
new file mode 100644
index 00000000000..1b8cc4efeb1
--- /dev/null
+++ b/homeassistant/components/smlight/config_flow.py
@@ -0,0 +1,151 @@
+"""Config flow for SMLIGHT Zigbee integration."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from pysmlight import Api2
+from pysmlight.exceptions import SmlightAuthError, SmlightConnectionError
+import voluptuous as vol
+
+from homeassistant.components import zeroconf
+from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
+from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
+from homeassistant.helpers.device_registry import format_mac
+
+from .const import DOMAIN
+
+STEP_USER_DATA_SCHEMA = vol.Schema(
+    {
+        vol.Required(CONF_HOST): str,
+    }
+)
+
+STEP_AUTH_DATA_SCHEMA = vol.Schema(
+    {
+        vol.Required(CONF_USERNAME): str,
+        vol.Required(CONF_PASSWORD): str,
+    }
+)
+
+
+class SmlightConfigFlow(ConfigFlow, domain=DOMAIN):
+    """Handle a config flow for SMLIGHT Zigbee."""
+
+    def __init__(self) -> None:
+        """Initialize the config flow."""
+        self.client: Api2
+        self.host: str | None = None
+
+    async def async_step_user(
+        self, user_input: dict[str, Any] | None = None
+    ) -> ConfigFlowResult:
+        """Handle the initial step."""
+        errors: dict[str, str] = {}
+
+        if user_input is not None:
+            host = user_input[CONF_HOST]
+            self.client = Api2(host, session=async_get_clientsession(self.hass))
+            self.host = host
+
+            try:
+                if not await self._async_check_auth_required(user_input):
+                    return await self._async_complete_entry(user_input)
+            except SmlightConnectionError:
+                errors["base"] = "cannot_connect"
+            except SmlightAuthError:
+                return await self.async_step_auth()
+
+        return self.async_show_form(
+            step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
+        )
+
+    async def async_step_auth(
+        self, user_input: dict[str, Any] | None = None
+    ) -> ConfigFlowResult:
+        """Handle authentication to SLZB-06 device."""
+        errors: dict[str, str] = {}
+
+        if user_input is not None:
+            try:
+                if not await self._async_check_auth_required(user_input):
+                    return await self._async_complete_entry(user_input)
+            except SmlightConnectionError:
+                return self.async_abort(reason="cannot_connect")
+            except SmlightAuthError:
+                errors["base"] = "invalid_auth"
+
+        return self.async_show_form(
+            step_id="auth", data_schema=STEP_AUTH_DATA_SCHEMA, errors=errors
+        )
+
+    async def async_step_zeroconf(
+        self, discovery_info: zeroconf.ZeroconfServiceInfo
+    ) -> ConfigFlowResult:
+        """Handle a discovered Lan coordinator."""
+        local_name = discovery_info.hostname[:-1]
+        node_name = local_name.removesuffix(".local")
+
+        self.host = local_name
+        self.context["title_placeholders"] = {CONF_NAME: node_name}
+        self.client = Api2(self.host, session=async_get_clientsession(self.hass))
+
+        mac = discovery_info.properties.get("mac")
+        # fallback for legacy firmware
+        if mac is None:
+            info = await self.client.get_info()
+            mac = info.MAC
+        await self.async_set_unique_id(format_mac(mac))
+        self._abort_if_unique_id_configured()
+
+        return await self.async_step_confirm_discovery()
+
+    async def async_step_confirm_discovery(
+        self, user_input: dict[str, Any] | None = None
+    ) -> ConfigFlowResult:
+        """Handle discovery confirm."""
+        errors: dict[str, str] = {}
+
+        if user_input is not None:
+            user_input[CONF_HOST] = self.host
+            try:
+                if not await self._async_check_auth_required(user_input):
+                    return await self._async_complete_entry(user_input)
+
+            except SmlightConnectionError:
+                return self.async_abort(reason="cannot_connect")
+
+            except SmlightAuthError:
+                return await self.async_step_auth()
+
+        self._set_confirm_only()
+
+        return self.async_show_form(
+            step_id="confirm_discovery",
+            description_placeholders={"host": self.host},
+            errors=errors,
+        )
+
+    async def _async_check_auth_required(self, user_input: dict[str, Any]) -> bool:
+        """Check if auth required and attempt to authenticate."""
+        if await self.client.check_auth_needed():
+            if user_input.get(CONF_USERNAME) and user_input.get(CONF_PASSWORD):
+                return not await self.client.authenticate(
+                    user_input[CONF_USERNAME], user_input[CONF_PASSWORD]
+                )
+            raise SmlightAuthError
+        return False
+
+    async def _async_complete_entry(
+        self, user_input: dict[str, Any]
+    ) -> ConfigFlowResult:
+        info = await self.client.get_info()
+        await self.async_set_unique_id(format_mac(info.MAC))
+        self._abort_if_unique_id_configured()
+
+        if user_input.get(CONF_HOST) is None:
+            user_input[CONF_HOST] = self.host
+
+        assert info.model is not None
+        return self.async_create_entry(title=info.model, data=user_input)
diff --git a/homeassistant/components/smlight/const.py b/homeassistant/components/smlight/const.py
new file mode 100644
index 00000000000..de3270fe3be
--- /dev/null
+++ b/homeassistant/components/smlight/const.py
@@ -0,0 +1,11 @@
+"""Constants for the SMLIGHT Zigbee integration."""
+
+from datetime import timedelta
+import logging
+
+DOMAIN = "smlight"
+
+ATTR_MANUFACTURER = "SMLIGHT"
+
+LOGGER = logging.getLogger(__package__)
+SCAN_INTERVAL = timedelta(seconds=300)
diff --git a/homeassistant/components/smlight/coordinator.py b/homeassistant/components/smlight/coordinator.py
new file mode 100644
index 00000000000..6a29f14fafd
--- /dev/null
+++ b/homeassistant/components/smlight/coordinator.py
@@ -0,0 +1,71 @@
+"""DataUpdateCoordinator for Smlight."""
+
+from dataclasses import dataclass
+
+from pysmlight import Api2, Info, Sensors
+from pysmlight.exceptions import SmlightAuthError, SmlightConnectionError
+
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
+from homeassistant.core import HomeAssistant
+from homeassistant.exceptions import ConfigEntryError
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
+from homeassistant.helpers.device_registry import format_mac
+from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
+
+from .const import DOMAIN, LOGGER, SCAN_INTERVAL
+
+
+@dataclass
+class SmData:
+    """SMLIGHT data stored in the DataUpdateCoordinator."""
+
+    sensors: Sensors
+    info: Info
+
+
+class SmDataUpdateCoordinator(DataUpdateCoordinator[SmData]):
+    """Class to manage fetching SMLIGHT data."""
+
+    config_entry: ConfigEntry
+
+    def __init__(self, hass: HomeAssistant, host: str) -> None:
+        """Initialize the coordinator."""
+        super().__init__(
+            hass,
+            LOGGER,
+            name=f"{DOMAIN}_{host}",
+            update_interval=SCAN_INTERVAL,
+        )
+
+        self.unique_id: str | None = None
+        self.client = Api2(host=host, session=async_get_clientsession(hass))
+
+    async def _async_setup(self) -> None:
+        """Authenticate if needed during initial setup."""
+        if await self.client.check_auth_needed():
+            if (
+                CONF_USERNAME in self.config_entry.data
+                and CONF_PASSWORD in self.config_entry.data
+            ):
+                try:
+                    await self.client.authenticate(
+                        self.config_entry.data[CONF_USERNAME],
+                        self.config_entry.data[CONF_PASSWORD],
+                    )
+                except SmlightAuthError as err:
+                    LOGGER.error("Failed to authenticate: %s", err)
+                    raise ConfigEntryError from err
+
+        info = await self.client.get_info()
+        self.unique_id = format_mac(info.MAC)
+
+    async def _async_update_data(self) -> SmData:
+        """Fetch data from the SMLIGHT device."""
+        try:
+            return SmData(
+                sensors=await self.client.get_sensors(),
+                info=await self.client.get_info(),
+            )
+        except SmlightConnectionError as err:
+            raise UpdateFailed(err) from err
diff --git a/homeassistant/components/smlight/entity.py b/homeassistant/components/smlight/entity.py
new file mode 100644
index 00000000000..50767d3bf74
--- /dev/null
+++ b/homeassistant/components/smlight/entity.py
@@ -0,0 +1,31 @@
+"""Base class for all SMLIGHT entities."""
+
+from __future__ import annotations
+
+from homeassistant.helpers.device_registry import (
+    CONNECTION_NETWORK_MAC,
+    DeviceInfo,
+    format_mac,
+)
+from homeassistant.helpers.update_coordinator import CoordinatorEntity
+
+from .const import ATTR_MANUFACTURER
+from .coordinator import SmDataUpdateCoordinator
+
+
+class SmEntity(CoordinatorEntity[SmDataUpdateCoordinator]):
+    """Base class for all SMLight entities."""
+
+    _attr_has_entity_name = True
+
+    def __init__(self, coordinator: SmDataUpdateCoordinator) -> None:
+        """Initialize entity with device."""
+        super().__init__(coordinator)
+        mac = format_mac(coordinator.data.info.MAC)
+        self._attr_device_info = DeviceInfo(
+            configuration_url=f"http://{coordinator.client.host}",
+            connections={(CONNECTION_NETWORK_MAC, mac)},
+            manufacturer=ATTR_MANUFACTURER,
+            model=coordinator.data.info.model,
+            sw_version=f"core: {coordinator.data.info.sw_version} / zigbee: {coordinator.data.info.zb_version}",
+        )
diff --git a/homeassistant/components/smlight/manifest.json b/homeassistant/components/smlight/manifest.json
new file mode 100644
index 00000000000..0dbd25e90bd
--- /dev/null
+++ b/homeassistant/components/smlight/manifest.json
@@ -0,0 +1,15 @@
+{
+  "domain": "smlight",
+  "name": "SMLIGHT SLZB",
+  "codeowners": ["@tl-sl"],
+  "config_flow": true,
+  "documentation": "https://www.home-assistant.io/integrations/smlight",
+  "integration_type": "device",
+  "iot_class": "local_polling",
+  "requirements": ["pysmlight==0.0.12"],
+  "zeroconf": [
+    {
+      "type": "_slzb-06._tcp.local."
+    }
+  ]
+}
diff --git a/homeassistant/components/smlight/sensor.py b/homeassistant/components/smlight/sensor.py
new file mode 100644
index 00000000000..d9c03760fb8
--- /dev/null
+++ b/homeassistant/components/smlight/sensor.py
@@ -0,0 +1,103 @@
+"""Support for SLZB-06 sensors."""
+
+from __future__ import annotations
+
+from collections.abc import Callable
+from dataclasses import dataclass
+
+from pysmlight import Sensors
+
+from homeassistant.components.sensor import (
+    SensorDeviceClass,
+    SensorEntity,
+    SensorEntityDescription,
+    SensorStateClass,
+)
+from homeassistant.const import EntityCategory, UnitOfInformation, UnitOfTemperature
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers.entity_platform import AddEntitiesCallback
+
+from . import SmConfigEntry
+from .coordinator import SmDataUpdateCoordinator
+from .entity import SmEntity
+
+
+@dataclass(frozen=True, kw_only=True)
+class SmSensorEntityDescription(SensorEntityDescription):
+    """Class describing SMLIGHT sensor entities."""
+
+    entity_category = EntityCategory.DIAGNOSTIC
+    value_fn: Callable[[Sensors], float | None]
+
+
+SENSORS = [
+    SmSensorEntityDescription(
+        key="core_temperature",
+        translation_key="core_temperature",
+        device_class=SensorDeviceClass.TEMPERATURE,
+        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
+        state_class=SensorStateClass.MEASUREMENT,
+        suggested_display_precision=1,
+        value_fn=lambda x: x.esp32_temp,
+    ),
+    SmSensorEntityDescription(
+        key="zigbee_temperature",
+        translation_key="zigbee_temperature",
+        device_class=SensorDeviceClass.TEMPERATURE,
+        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
+        state_class=SensorStateClass.MEASUREMENT,
+        suggested_display_precision=1,
+        value_fn=lambda x: x.zb_temp,
+    ),
+    SmSensorEntityDescription(
+        key="ram_usage",
+        translation_key="ram_usage",
+        device_class=SensorDeviceClass.DATA_SIZE,
+        native_unit_of_measurement=UnitOfInformation.KILOBYTES,
+        entity_registry_enabled_default=False,
+        value_fn=lambda x: x.ram_usage,
+    ),
+    SmSensorEntityDescription(
+        key="fs_usage",
+        translation_key="fs_usage",
+        device_class=SensorDeviceClass.DATA_SIZE,
+        native_unit_of_measurement=UnitOfInformation.KILOBYTES,
+        entity_registry_enabled_default=False,
+        value_fn=lambda x: x.fs_used,
+    ),
+]
+
+
+async def async_setup_entry(
+    hass: HomeAssistant,
+    entry: SmConfigEntry,
+    async_add_entities: AddEntitiesCallback,
+) -> None:
+    """Set up SMLIGHT sensor based on a config entry."""
+    coordinator = entry.runtime_data
+
+    async_add_entities(
+        SmSensorEntity(coordinator, description) for description in SENSORS
+    )
+
+
+class SmSensorEntity(SmEntity, SensorEntity):
+    """Representation of a slzb sensor."""
+
+    entity_description: SmSensorEntityDescription
+
+    def __init__(
+        self,
+        coordinator: SmDataUpdateCoordinator,
+        description: SmSensorEntityDescription,
+    ) -> None:
+        """Initiate slzb sensor."""
+        super().__init__(coordinator)
+
+        self.entity_description = description
+        self._attr_unique_id = f"{coordinator.unique_id}_{description.key}"
+
+    @property
+    def native_value(self) -> float | None:
+        """Return the sensor value."""
+        return self.entity_description.value_fn(self.coordinator.data.sensors)
diff --git a/homeassistant/components/smlight/strings.json b/homeassistant/components/smlight/strings.json
new file mode 100644
index 00000000000..02b9ebcc4e8
--- /dev/null
+++ b/homeassistant/components/smlight/strings.json
@@ -0,0 +1,49 @@
+{
+  "config": {
+    "step": {
+      "user": {
+        "description": "Set up SMLIGHT Zigbee Integration",
+        "data": {
+          "host": "[%key:common::config_flow::data::host%]"
+        },
+        "data_description": {
+          "host": "The hostname or IP address of the SMLIGHT SLZB-06x device"
+        }
+      },
+      "auth": {
+        "description": "Please enter the username and password",
+        "data": {
+          "username": "[%key:common::config_flow::data::username%]",
+          "password": "[%key:common::config_flow::data::password%]"
+        }
+      },
+      "confirm_discovery": {
+        "description": "Do you want to set up SMLIGHT at {host}?"
+      }
+    },
+    "error": {
+      "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
+      "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
+      "unknown": "[%key:common::config_flow::error::unknown%]"
+    },
+    "abort": {
+      "already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
+    }
+  },
+  "entity": {
+    "sensor": {
+      "zigbee_temperature": {
+        "name": "Zigbee chip temp"
+      },
+      "core_temperature": {
+        "name": "Core chip temp"
+      },
+      "fs_usage": {
+        "name": "Filesystem usage"
+      },
+      "ram_usage": {
+        "name": "RAM usage"
+      }
+    }
+  }
+}
diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py
index b474fbaf54f..eecb3a76aac 100644
--- a/homeassistant/generated/config_flows.py
+++ b/homeassistant/generated/config_flows.py
@@ -525,6 +525,7 @@ FLOWS = {
         "smartthings",
         "smarttub",
         "smhi",
+        "smlight",
         "sms",
         "snapcast",
         "snooz",
diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json
index 9e1250e3b60..b14531e1731 100644
--- a/homeassistant/generated/integrations.json
+++ b/homeassistant/generated/integrations.json
@@ -5588,6 +5588,12 @@
       "config_flow": true,
       "iot_class": "cloud_polling"
     },
+    "smlight": {
+      "name": "SMLIGHT SLZB",
+      "integration_type": "device",
+      "config_flow": true,
+      "iot_class": "local_polling"
+    },
     "sms": {
       "name": "SMS notifications via GSM-modem",
       "integration_type": "hub",
diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py
index 7cd60da2d0e..3e5e34090d1 100644
--- a/homeassistant/generated/zeroconf.py
+++ b/homeassistant/generated/zeroconf.py
@@ -747,6 +747,9 @@ ZEROCONF = {
         },
     ],
     "_slzb-06._tcp.local.": [
+        {
+            "domain": "smlight",
+        },
         {
             "domain": "zha",
             "name": "slzb-06*",
diff --git a/requirements_all.txt b/requirements_all.txt
index f640bb15e5b..ce1db53c844 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -2210,6 +2210,9 @@ pysmartthings==0.7.8
 # homeassistant.components.edl21
 pysml==0.0.12
 
+# homeassistant.components.smlight
+pysmlight==0.0.12
+
 # homeassistant.components.snmp
 pysnmp==6.2.5
 
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index f4db2963fe2..ea248a662bd 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -1764,6 +1764,9 @@ pysmartthings==0.7.8
 # homeassistant.components.edl21
 pysml==0.0.12
 
+# homeassistant.components.smlight
+pysmlight==0.0.12
+
 # homeassistant.components.snmp
 pysnmp==6.2.5
 
diff --git a/tests/components/smlight/__init__.py b/tests/components/smlight/__init__.py
new file mode 100644
index 00000000000..37184226507
--- /dev/null
+++ b/tests/components/smlight/__init__.py
@@ -0,0 +1 @@
+"""Tests for the SMLIGHT Zigbee adapter integration."""
diff --git a/tests/components/smlight/conftest.py b/tests/components/smlight/conftest.py
new file mode 100644
index 00000000000..0338bf4b672
--- /dev/null
+++ b/tests/components/smlight/conftest.py
@@ -0,0 +1,74 @@
+"""Common fixtures for the SMLIGHT Zigbee tests."""
+
+from collections.abc import Generator
+from unittest.mock import AsyncMock, MagicMock, patch
+
+from pysmlight.web import Info, Sensors
+import pytest
+
+from homeassistant.components.smlight.const import DOMAIN
+from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
+from homeassistant.core import HomeAssistant
+
+from tests.common import MockConfigEntry, load_json_object_fixture
+
+MOCK_HOST = "slzb-06.local"
+MOCK_USERNAME = "test-user"
+MOCK_PASSWORD = "test-pass"
+
+
+@pytest.fixture
+def mock_config_entry() -> MockConfigEntry:
+    """Return the default mocked config entry."""
+    return MockConfigEntry(
+        domain=DOMAIN,
+        data={
+            CONF_HOST: MOCK_HOST,
+            CONF_USERNAME: MOCK_USERNAME,
+            CONF_PASSWORD: MOCK_PASSWORD,
+        },
+        unique_id="aa:bb:cc:dd:ee:ff",
+    )
+
+
+@pytest.fixture
+def mock_setup_entry() -> Generator[AsyncMock, None, None]:
+    """Override async_setup_entry."""
+    with patch(
+        "homeassistant.components.smlight.async_setup_entry", return_value=True
+    ) as mock_setup_entry:
+        yield mock_setup_entry
+
+
+@pytest.fixture
+def mock_smlight_client(request: pytest.FixtureRequest) -> Generator[MagicMock]:
+    """Mock the SMLIGHT API client."""
+    with (
+        patch(
+            "homeassistant.components.smlight.coordinator.Api2", autospec=True
+        ) as smlight_mock,
+        patch("homeassistant.components.smlight.config_flow.Api2", new=smlight_mock),
+    ):
+        api = smlight_mock.return_value
+        api.host = MOCK_HOST
+        api.get_info.return_value = Info.from_dict(
+            load_json_object_fixture("info.json", DOMAIN)
+        )
+        api.get_sensors.return_value = Sensors.from_dict(
+            load_json_object_fixture("sensors.json", DOMAIN)
+        )
+
+        api.check_auth_needed.return_value = False
+        api.authenticate.return_value = True
+
+        yield api
+
+
+async def setup_integration(hass: HomeAssistant, mock_config_entry: MockConfigEntry):
+    """Set up the integration."""
+    mock_config_entry.add_to_hass(hass)
+
+    await hass.config_entries.async_setup(mock_config_entry.entry_id)
+    await hass.async_block_till_done()
+
+    return mock_config_entry
diff --git a/tests/components/smlight/fixtures/info.json b/tests/components/smlight/fixtures/info.json
new file mode 100644
index 00000000000..72bb7c1ed9b
--- /dev/null
+++ b/tests/components/smlight/fixtures/info.json
@@ -0,0 +1,16 @@
+{
+  "coord_mode": 0,
+  "device_ip": "192.168.1.161",
+  "fs_total": 3456,
+  "fw_channel": "dev",
+  "MAC": "AA:BB:CC:DD:EE:FF",
+  "model": "SLZB-06p7",
+  "ram_total": 296,
+  "sw_version": "v2.3.1.dev",
+  "wifi_mode": 0,
+  "zb_flash_size": 704,
+  "zb_hw": "CC2652P7",
+  "zb_ram_size": 152,
+  "zb_version": -1,
+  "zb_type": -1
+}
diff --git a/tests/components/smlight/fixtures/sensors.json b/tests/components/smlight/fixtures/sensors.json
new file mode 100644
index 00000000000..0b2f9055e01
--- /dev/null
+++ b/tests/components/smlight/fixtures/sensors.json
@@ -0,0 +1,14 @@
+{
+  "esp32_temp": 35.0,
+  "zb_temp": 32.7,
+  "uptime": 508125,
+  "socket_uptime": 127,
+  "ram_usage": 99,
+  "fs_used": 188,
+  "ethernet": true,
+  "wifi_connected": false,
+  "wifi_status": 255,
+  "disable_leds": false,
+  "night_mode": false,
+  "auto_zigbee": false
+}
diff --git a/tests/components/smlight/snapshots/test_init.ambr b/tests/components/smlight/snapshots/test_init.ambr
new file mode 100644
index 00000000000..528a7b7b340
--- /dev/null
+++ b/tests/components/smlight/snapshots/test_init.ambr
@@ -0,0 +1,33 @@
+# serializer version: 1
+# name: test_device_info
+  DeviceRegistryEntrySnapshot({
+    'area_id': None,
+    'config_entries': <ANY>,
+    'configuration_url': 'http://slzb-06.local',
+    'connections': set({
+      tuple(
+        'mac',
+        'aa:bb:cc:dd:ee:ff',
+      ),
+    }),
+    'disabled_by': None,
+    'entry_type': None,
+    'hw_version': None,
+    'id': <ANY>,
+    'identifiers': set({
+    }),
+    'is_new': False,
+    'labels': set({
+    }),
+    'manufacturer': 'SMLIGHT',
+    'model': 'SLZB-06p7',
+    'model_id': None,
+    'name': 'Mock Title',
+    'name_by_user': None,
+    'primary_config_entry': <ANY>,
+    'serial_number': None,
+    'suggested_area': None,
+    'sw_version': 'core: v2.3.1.dev / zigbee: -1',
+    'via_device_id': None,
+  })
+# ---
diff --git a/tests/components/smlight/snapshots/test_sensor.ambr b/tests/components/smlight/snapshots/test_sensor.ambr
new file mode 100644
index 00000000000..0ff3d37b735
--- /dev/null
+++ b/tests/components/smlight/snapshots/test_sensor.ambr
@@ -0,0 +1,741 @@
+# serializer version: 1
+# name: test_sensors[sensor.mock_title_core_chip_temp-entry]
+  EntityRegistryEntrySnapshot({
+    'aliases': set({
+    }),
+    'area_id': None,
+    'capabilities': dict({
+      'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
+    }),
+    'config_entry_id': <ANY>,
+    'device_class': None,
+    'device_id': <ANY>,
+    'disabled_by': None,
+    'domain': 'sensor',
+    'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
+    'entity_id': 'sensor.mock_title_core_chip_temp',
+    'has_entity_name': True,
+    'hidden_by': None,
+    'icon': None,
+    'id': <ANY>,
+    'labels': set({
+    }),
+    'name': None,
+    'options': dict({
+      'sensor': dict({
+        'suggested_display_precision': 1,
+      }),
+    }),
+    'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
+    'original_icon': None,
+    'original_name': 'Core chip temp',
+    'platform': 'smlight',
+    'previous_unique_id': None,
+    'supported_features': 0,
+    'translation_key': 'core_temperature',
+    'unique_id': 'aa:bb:cc:dd:ee:ff_core_temperature',
+    'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
+  })
+# ---
+# name: test_sensors[sensor.mock_title_core_chip_temp-state]
+  StateSnapshot({
+    'attributes': ReadOnlyDict({
+      'device_class': 'temperature',
+      'friendly_name': 'Mock Title Core chip temp',
+      'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
+      'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
+    }),
+    'context': <ANY>,
+    'entity_id': 'sensor.mock_title_core_chip_temp',
+    'last_changed': <ANY>,
+    'last_reported': <ANY>,
+    'last_updated': <ANY>,
+    'state': '35.0',
+  })
+# ---
+# name: test_sensors[sensor.mock_title_filesystem_usage-entry]
+  EntityRegistryEntrySnapshot({
+    'aliases': set({
+    }),
+    'area_id': None,
+    'capabilities': None,
+    'config_entry_id': <ANY>,
+    'device_class': None,
+    'device_id': <ANY>,
+    'disabled_by': None,
+    'domain': 'sensor',
+    'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
+    'entity_id': 'sensor.mock_title_filesystem_usage',
+    'has_entity_name': True,
+    'hidden_by': None,
+    'icon': None,
+    'id': <ANY>,
+    'labels': set({
+    }),
+    'name': None,
+    'options': dict({
+    }),
+    'original_device_class': <SensorDeviceClass.DATA_SIZE: 'data_size'>,
+    'original_icon': None,
+    'original_name': 'Filesystem usage',
+    'platform': 'smlight',
+    'previous_unique_id': None,
+    'supported_features': 0,
+    'translation_key': 'fs_usage',
+    'unique_id': 'aa:bb:cc:dd:ee:ff_fs_usage',
+    'unit_of_measurement': <UnitOfInformation.KILOBYTES: 'kB'>,
+  })
+# ---
+# name: test_sensors[sensor.mock_title_filesystem_usage-state]
+  StateSnapshot({
+    'attributes': ReadOnlyDict({
+      'device_class': 'data_size',
+      'friendly_name': 'Mock Title Filesystem usage',
+      'unit_of_measurement': <UnitOfInformation.KILOBYTES: 'kB'>,
+    }),
+    'context': <ANY>,
+    'entity_id': 'sensor.mock_title_filesystem_usage',
+    'last_changed': <ANY>,
+    'last_reported': <ANY>,
+    'last_updated': <ANY>,
+    'state': '188',
+  })
+# ---
+# name: test_sensors[sensor.mock_title_ram_usage-entry]
+  EntityRegistryEntrySnapshot({
+    'aliases': set({
+    }),
+    'area_id': None,
+    'capabilities': None,
+    'config_entry_id': <ANY>,
+    'device_class': None,
+    'device_id': <ANY>,
+    'disabled_by': None,
+    'domain': 'sensor',
+    'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
+    'entity_id': 'sensor.mock_title_ram_usage',
+    'has_entity_name': True,
+    'hidden_by': None,
+    'icon': None,
+    'id': <ANY>,
+    'labels': set({
+    }),
+    'name': None,
+    'options': dict({
+    }),
+    'original_device_class': <SensorDeviceClass.DATA_SIZE: 'data_size'>,
+    'original_icon': None,
+    'original_name': 'RAM usage',
+    'platform': 'smlight',
+    'previous_unique_id': None,
+    'supported_features': 0,
+    'translation_key': 'ram_usage',
+    'unique_id': 'aa:bb:cc:dd:ee:ff_ram_usage',
+    'unit_of_measurement': <UnitOfInformation.KILOBYTES: 'kB'>,
+  })
+# ---
+# name: test_sensors[sensor.mock_title_ram_usage-state]
+  StateSnapshot({
+    'attributes': ReadOnlyDict({
+      'device_class': 'data_size',
+      'friendly_name': 'Mock Title RAM usage',
+      'unit_of_measurement': <UnitOfInformation.KILOBYTES: 'kB'>,
+    }),
+    'context': <ANY>,
+    'entity_id': 'sensor.mock_title_ram_usage',
+    'last_changed': <ANY>,
+    'last_reported': <ANY>,
+    'last_updated': <ANY>,
+    'state': '99',
+  })
+# ---
+# name: test_sensors[sensor.mock_title_zigbee_chip_temp-entry]
+  EntityRegistryEntrySnapshot({
+    'aliases': set({
+    }),
+    'area_id': None,
+    'capabilities': dict({
+      'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
+    }),
+    'config_entry_id': <ANY>,
+    'device_class': None,
+    'device_id': <ANY>,
+    'disabled_by': None,
+    'domain': 'sensor',
+    'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
+    'entity_id': 'sensor.mock_title_zigbee_chip_temp',
+    'has_entity_name': True,
+    'hidden_by': None,
+    'icon': None,
+    'id': <ANY>,
+    'labels': set({
+    }),
+    'name': None,
+    'options': dict({
+      'sensor': dict({
+        'suggested_display_precision': 1,
+      }),
+    }),
+    'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
+    'original_icon': None,
+    'original_name': 'Zigbee chip temp',
+    'platform': 'smlight',
+    'previous_unique_id': None,
+    'supported_features': 0,
+    'translation_key': 'zigbee_temperature',
+    'unique_id': 'aa:bb:cc:dd:ee:ff_zigbee_temperature',
+    'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
+  })
+# ---
+# name: test_sensors[sensor.mock_title_zigbee_chip_temp-state]
+  StateSnapshot({
+    'attributes': ReadOnlyDict({
+      'device_class': 'temperature',
+      'friendly_name': 'Mock Title Zigbee chip temp',
+      'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
+      'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
+    }),
+    'context': <ANY>,
+    'entity_id': 'sensor.mock_title_zigbee_chip_temp',
+    'last_changed': <ANY>,
+    'last_reported': <ANY>,
+    'last_updated': <ANY>,
+    'state': '32.7',
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_core_chip_temp-entry]
+  EntityRegistryEntrySnapshot({
+    'aliases': set({
+    }),
+    'area_id': None,
+    'capabilities': dict({
+      'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
+    }),
+    'config_entry_id': <ANY>,
+    'device_class': None,
+    'device_id': <ANY>,
+    'disabled_by': None,
+    'domain': 'sensor',
+    'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
+    'entity_id': 'sensor.slzb_06_core_chip_temp',
+    'has_entity_name': True,
+    'hidden_by': None,
+    'icon': None,
+    'id': <ANY>,
+    'labels': set({
+    }),
+    'name': None,
+    'options': dict({
+      'sensor': dict({
+        'suggested_display_precision': 1,
+      }),
+    }),
+    'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
+    'original_icon': None,
+    'original_name': 'Core chip temp',
+    'platform': 'smlight',
+    'previous_unique_id': None,
+    'supported_features': 0,
+    'translation_key': 'core_temperature',
+    'unique_id': 'aa:bb:cc:dd:ee:ff_core_temperature',
+    'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_core_chip_temp-state]
+  StateSnapshot({
+    'attributes': ReadOnlyDict({
+      'device_class': 'temperature',
+      'friendly_name': 'slzb-06 Core chip temp',
+      'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
+      'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
+    }),
+    'context': <ANY>,
+    'entity_id': 'sensor.slzb_06_core_chip_temp',
+    'last_changed': <ANY>,
+    'last_reported': <ANY>,
+    'last_updated': <ANY>,
+    'state': '35.0',
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_core_chip_temp]
+  StateSnapshot({
+    'attributes': ReadOnlyDict({
+      'device_class': 'temperature',
+      'friendly_name': 'slzb-06 Core chip temp',
+      'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
+      'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
+    }),
+    'context': <ANY>,
+    'entity_id': 'sensor.slzb_06_core_chip_temp',
+    'last_changed': <ANY>,
+    'last_reported': <ANY>,
+    'last_updated': <ANY>,
+    'state': '35.0',
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_core_chip_temp].1
+  EntityRegistryEntrySnapshot({
+    'aliases': set({
+    }),
+    'area_id': None,
+    'capabilities': dict({
+      'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
+    }),
+    'config_entry_id': <ANY>,
+    'device_class': None,
+    'device_id': <ANY>,
+    'disabled_by': None,
+    'domain': 'sensor',
+    'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
+    'entity_id': 'sensor.slzb_06_core_chip_temp',
+    'has_entity_name': True,
+    'hidden_by': None,
+    'icon': None,
+    'id': <ANY>,
+    'labels': set({
+    }),
+    'name': None,
+    'options': dict({
+      'sensor': dict({
+        'suggested_display_precision': 1,
+      }),
+    }),
+    'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
+    'original_icon': None,
+    'original_name': 'Core chip temp',
+    'platform': 'smlight',
+    'previous_unique_id': None,
+    'supported_features': 0,
+    'translation_key': 'core_temperature',
+    'unique_id': 'aa:bb:cc:dd:ee:ff_core_temperature',
+    'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_core_chip_temp].2
+  DeviceRegistryEntrySnapshot({
+    'area_id': None,
+    'config_entries': <ANY>,
+    'configuration_url': 'http://slzb-06.local',
+    'connections': set({
+      tuple(
+        'mac',
+        'aa:bb:cc:dd:ee:ff',
+      ),
+    }),
+    'disabled_by': None,
+    'entry_type': None,
+    'hw_version': None,
+    'id': <ANY>,
+    'identifiers': set({
+    }),
+    'is_new': False,
+    'labels': set({
+    }),
+    'manufacturer': 'SMLIGHT',
+    'model': 'SLZB-06p7',
+    'model_id': None,
+    'name': 'slzb-06',
+    'name_by_user': None,
+    'primary_config_entry': <ANY>,
+    'serial_number': None,
+    'suggested_area': None,
+    'sw_version': 'core: v2.3.1.dev / zigbee: -1',
+    'via_device_id': None,
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_filesystem_usage-entry]
+  EntityRegistryEntrySnapshot({
+    'aliases': set({
+    }),
+    'area_id': None,
+    'capabilities': None,
+    'config_entry_id': <ANY>,
+    'device_class': None,
+    'device_id': <ANY>,
+    'disabled_by': None,
+    'domain': 'sensor',
+    'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
+    'entity_id': 'sensor.slzb_06_filesystem_usage',
+    'has_entity_name': True,
+    'hidden_by': None,
+    'icon': None,
+    'id': <ANY>,
+    'labels': set({
+    }),
+    'name': None,
+    'options': dict({
+    }),
+    'original_device_class': <SensorDeviceClass.DATA_SIZE: 'data_size'>,
+    'original_icon': None,
+    'original_name': 'Filesystem usage',
+    'platform': 'smlight',
+    'previous_unique_id': None,
+    'supported_features': 0,
+    'translation_key': 'fs_usage',
+    'unique_id': 'aa:bb:cc:dd:ee:ff_fs_usage',
+    'unit_of_measurement': <UnitOfInformation.KILOBYTES: 'kB'>,
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_filesystem_usage-state]
+  StateSnapshot({
+    'attributes': ReadOnlyDict({
+      'device_class': 'data_size',
+      'friendly_name': 'slzb-06 Filesystem usage',
+      'unit_of_measurement': <UnitOfInformation.KILOBYTES: 'kB'>,
+    }),
+    'context': <ANY>,
+    'entity_id': 'sensor.slzb_06_filesystem_usage',
+    'last_changed': <ANY>,
+    'last_reported': <ANY>,
+    'last_updated': <ANY>,
+    'state': '188',
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_filesystem_usage]
+  StateSnapshot({
+    'attributes': ReadOnlyDict({
+      'device_class': 'data_size',
+      'friendly_name': 'slzb-06 Filesystem usage',
+      'unit_of_measurement': <UnitOfInformation.KILOBYTES: 'kB'>,
+    }),
+    'context': <ANY>,
+    'entity_id': 'sensor.slzb_06_filesystem_usage',
+    'last_changed': <ANY>,
+    'last_reported': <ANY>,
+    'last_updated': <ANY>,
+    'state': '188',
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_filesystem_usage].1
+  EntityRegistryEntrySnapshot({
+    'aliases': set({
+    }),
+    'area_id': None,
+    'capabilities': None,
+    'config_entry_id': <ANY>,
+    'device_class': None,
+    'device_id': <ANY>,
+    'disabled_by': None,
+    'domain': 'sensor',
+    'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
+    'entity_id': 'sensor.slzb_06_filesystem_usage',
+    'has_entity_name': True,
+    'hidden_by': None,
+    'icon': None,
+    'id': <ANY>,
+    'labels': set({
+    }),
+    'name': None,
+    'options': dict({
+    }),
+    'original_device_class': <SensorDeviceClass.DATA_SIZE: 'data_size'>,
+    'original_icon': None,
+    'original_name': 'Filesystem usage',
+    'platform': 'smlight',
+    'previous_unique_id': None,
+    'supported_features': 0,
+    'translation_key': 'fs_usage',
+    'unique_id': 'aa:bb:cc:dd:ee:ff_fs_usage',
+    'unit_of_measurement': <UnitOfInformation.KILOBYTES: 'kB'>,
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_filesystem_usage].2
+  DeviceRegistryEntrySnapshot({
+    'area_id': None,
+    'config_entries': <ANY>,
+    'configuration_url': 'http://slzb-06.local',
+    'connections': set({
+      tuple(
+        'mac',
+        'aa:bb:cc:dd:ee:ff',
+      ),
+    }),
+    'disabled_by': None,
+    'entry_type': None,
+    'hw_version': None,
+    'id': <ANY>,
+    'identifiers': set({
+    }),
+    'is_new': False,
+    'labels': set({
+    }),
+    'manufacturer': 'SMLIGHT',
+    'model': 'SLZB-06p7',
+    'model_id': None,
+    'name': 'slzb-06',
+    'name_by_user': None,
+    'primary_config_entry': <ANY>,
+    'serial_number': None,
+    'suggested_area': None,
+    'sw_version': 'core: v2.3.1.dev / zigbee: -1',
+    'via_device_id': None,
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_ram_usage-entry]
+  EntityRegistryEntrySnapshot({
+    'aliases': set({
+    }),
+    'area_id': None,
+    'capabilities': None,
+    'config_entry_id': <ANY>,
+    'device_class': None,
+    'device_id': <ANY>,
+    'disabled_by': None,
+    'domain': 'sensor',
+    'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
+    'entity_id': 'sensor.slzb_06_ram_usage',
+    'has_entity_name': True,
+    'hidden_by': None,
+    'icon': None,
+    'id': <ANY>,
+    'labels': set({
+    }),
+    'name': None,
+    'options': dict({
+    }),
+    'original_device_class': <SensorDeviceClass.DATA_SIZE: 'data_size'>,
+    'original_icon': None,
+    'original_name': 'RAM usage',
+    'platform': 'smlight',
+    'previous_unique_id': None,
+    'supported_features': 0,
+    'translation_key': 'ram_usage',
+    'unique_id': 'aa:bb:cc:dd:ee:ff_ram_usage',
+    'unit_of_measurement': <UnitOfInformation.KILOBYTES: 'kB'>,
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_ram_usage-state]
+  StateSnapshot({
+    'attributes': ReadOnlyDict({
+      'device_class': 'data_size',
+      'friendly_name': 'slzb-06 RAM usage',
+      'unit_of_measurement': <UnitOfInformation.KILOBYTES: 'kB'>,
+    }),
+    'context': <ANY>,
+    'entity_id': 'sensor.slzb_06_ram_usage',
+    'last_changed': <ANY>,
+    'last_reported': <ANY>,
+    'last_updated': <ANY>,
+    'state': '99',
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_ram_usage]
+  StateSnapshot({
+    'attributes': ReadOnlyDict({
+      'device_class': 'data_size',
+      'friendly_name': 'slzb-06 RAM usage',
+      'unit_of_measurement': <UnitOfInformation.KILOBYTES: 'kB'>,
+    }),
+    'context': <ANY>,
+    'entity_id': 'sensor.slzb_06_ram_usage',
+    'last_changed': <ANY>,
+    'last_reported': <ANY>,
+    'last_updated': <ANY>,
+    'state': '99',
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_ram_usage].1
+  EntityRegistryEntrySnapshot({
+    'aliases': set({
+    }),
+    'area_id': None,
+    'capabilities': None,
+    'config_entry_id': <ANY>,
+    'device_class': None,
+    'device_id': <ANY>,
+    'disabled_by': None,
+    'domain': 'sensor',
+    'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
+    'entity_id': 'sensor.slzb_06_ram_usage',
+    'has_entity_name': True,
+    'hidden_by': None,
+    'icon': None,
+    'id': <ANY>,
+    'labels': set({
+    }),
+    'name': None,
+    'options': dict({
+    }),
+    'original_device_class': <SensorDeviceClass.DATA_SIZE: 'data_size'>,
+    'original_icon': None,
+    'original_name': 'RAM usage',
+    'platform': 'smlight',
+    'previous_unique_id': None,
+    'supported_features': 0,
+    'translation_key': 'ram_usage',
+    'unique_id': 'aa:bb:cc:dd:ee:ff_ram_usage',
+    'unit_of_measurement': <UnitOfInformation.KILOBYTES: 'kB'>,
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_ram_usage].2
+  DeviceRegistryEntrySnapshot({
+    'area_id': None,
+    'config_entries': <ANY>,
+    'configuration_url': 'http://slzb-06.local',
+    'connections': set({
+      tuple(
+        'mac',
+        'aa:bb:cc:dd:ee:ff',
+      ),
+    }),
+    'disabled_by': None,
+    'entry_type': None,
+    'hw_version': None,
+    'id': <ANY>,
+    'identifiers': set({
+    }),
+    'is_new': False,
+    'labels': set({
+    }),
+    'manufacturer': 'SMLIGHT',
+    'model': 'SLZB-06p7',
+    'model_id': None,
+    'name': 'slzb-06',
+    'name_by_user': None,
+    'primary_config_entry': <ANY>,
+    'serial_number': None,
+    'suggested_area': None,
+    'sw_version': 'core: v2.3.1.dev / zigbee: -1',
+    'via_device_id': None,
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_zigbee_chip_temp-entry]
+  EntityRegistryEntrySnapshot({
+    'aliases': set({
+    }),
+    'area_id': None,
+    'capabilities': dict({
+      'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
+    }),
+    'config_entry_id': <ANY>,
+    'device_class': None,
+    'device_id': <ANY>,
+    'disabled_by': None,
+    'domain': 'sensor',
+    'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
+    'entity_id': 'sensor.slzb_06_zigbee_chip_temp',
+    'has_entity_name': True,
+    'hidden_by': None,
+    'icon': None,
+    'id': <ANY>,
+    'labels': set({
+    }),
+    'name': None,
+    'options': dict({
+      'sensor': dict({
+        'suggested_display_precision': 1,
+      }),
+    }),
+    'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
+    'original_icon': None,
+    'original_name': 'Zigbee chip temp',
+    'platform': 'smlight',
+    'previous_unique_id': None,
+    'supported_features': 0,
+    'translation_key': 'zigbee_temperature',
+    'unique_id': 'aa:bb:cc:dd:ee:ff_zigbee_temperature',
+    'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_zigbee_chip_temp-state]
+  StateSnapshot({
+    'attributes': ReadOnlyDict({
+      'device_class': 'temperature',
+      'friendly_name': 'slzb-06 Zigbee chip temp',
+      'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
+      'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
+    }),
+    'context': <ANY>,
+    'entity_id': 'sensor.slzb_06_zigbee_chip_temp',
+    'last_changed': <ANY>,
+    'last_reported': <ANY>,
+    'last_updated': <ANY>,
+    'state': '32.7',
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_zigbee_chip_temp]
+  StateSnapshot({
+    'attributes': ReadOnlyDict({
+      'device_class': 'temperature',
+      'friendly_name': 'slzb-06 Zigbee chip temp',
+      'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
+      'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
+    }),
+    'context': <ANY>,
+    'entity_id': 'sensor.slzb_06_zigbee_chip_temp',
+    'last_changed': <ANY>,
+    'last_reported': <ANY>,
+    'last_updated': <ANY>,
+    'state': '32.7',
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_zigbee_chip_temp].1
+  EntityRegistryEntrySnapshot({
+    'aliases': set({
+    }),
+    'area_id': None,
+    'capabilities': dict({
+      'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
+    }),
+    'config_entry_id': <ANY>,
+    'device_class': None,
+    'device_id': <ANY>,
+    'disabled_by': None,
+    'domain': 'sensor',
+    'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
+    'entity_id': 'sensor.slzb_06_zigbee_chip_temp',
+    'has_entity_name': True,
+    'hidden_by': None,
+    'icon': None,
+    'id': <ANY>,
+    'labels': set({
+    }),
+    'name': None,
+    'options': dict({
+      'sensor': dict({
+        'suggested_display_precision': 1,
+      }),
+    }),
+    'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
+    'original_icon': None,
+    'original_name': 'Zigbee chip temp',
+    'platform': 'smlight',
+    'previous_unique_id': None,
+    'supported_features': 0,
+    'translation_key': 'zigbee_temperature',
+    'unique_id': 'aa:bb:cc:dd:ee:ff_zigbee_temperature',
+    'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
+  })
+# ---
+# name: test_sensors[sensor.slzb_06_zigbee_chip_temp].2
+  DeviceRegistryEntrySnapshot({
+    'area_id': None,
+    'config_entries': <ANY>,
+    'configuration_url': 'http://slzb-06.local',
+    'connections': set({
+      tuple(
+        'mac',
+        'aa:bb:cc:dd:ee:ff',
+      ),
+    }),
+    'disabled_by': None,
+    'entry_type': None,
+    'hw_version': None,
+    'id': <ANY>,
+    'identifiers': set({
+    }),
+    'is_new': False,
+    'labels': set({
+    }),
+    'manufacturer': 'SMLIGHT',
+    'model': 'SLZB-06p7',
+    'model_id': None,
+    'name': 'slzb-06',
+    'name_by_user': None,
+    'primary_config_entry': <ANY>,
+    'serial_number': None,
+    'suggested_area': None,
+    'sw_version': 'core: v2.3.1.dev / zigbee: -1',
+    'via_device_id': None,
+  })
+# ---
diff --git a/tests/components/smlight/test_config_flow.py b/tests/components/smlight/test_config_flow.py
new file mode 100644
index 00000000000..9a23a8de753
--- /dev/null
+++ b/tests/components/smlight/test_config_flow.py
@@ -0,0 +1,365 @@
+"""Test the SMLIGHT SLZB config flow."""
+
+from ipaddress import ip_address
+from unittest.mock import AsyncMock, MagicMock
+
+from pysmlight.exceptions import SmlightAuthError, SmlightConnectionError
+import pytest
+
+from homeassistant.components import zeroconf
+from homeassistant.components.smlight.const import DOMAIN
+from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF
+from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
+from homeassistant.core import HomeAssistant
+from homeassistant.data_entry_flow import FlowResultType
+
+from .conftest import MOCK_HOST, MOCK_PASSWORD, MOCK_USERNAME
+
+from tests.common import MockConfigEntry
+
+DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo(
+    ip_address=ip_address("127.0.0.1"),
+    ip_addresses=[ip_address("127.0.0.1")],
+    hostname="slzb-06.local.",
+    name="mock_name",
+    port=6638,
+    properties={"mac": "AA:BB:CC:DD:EE:FF"},
+    type="mock_type",
+)
+
+DISCOVERY_INFO_LEGACY = zeroconf.ZeroconfServiceInfo(
+    ip_address=ip_address("127.0.0.1"),
+    ip_addresses=[ip_address("127.0.0.1")],
+    hostname="slzb-06.local.",
+    name="mock_name",
+    port=6638,
+    properties={},
+    type="mock_type",
+)
+
+
+@pytest.mark.usefixtures("mock_smlight_client")
+async def test_user_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
+    """Test the full manual user flow."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": SOURCE_USER}
+    )
+    assert result["type"] is FlowResultType.FORM
+    assert result["step_id"] == "user"
+    assert result["errors"] == {}
+
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        {
+            CONF_HOST: MOCK_HOST,
+        },
+    )
+
+    assert result2["type"] is FlowResultType.CREATE_ENTRY
+    assert result2["title"] == "SLZB-06p7"
+    assert result2["data"] == {
+        CONF_HOST: MOCK_HOST,
+    }
+    assert result2["context"]["unique_id"] == "aa:bb:cc:dd:ee:ff"
+    assert len(mock_setup_entry.mock_calls) == 1
+
+
+async def test_zeroconf_flow(
+    hass: HomeAssistant,
+    mock_smlight_client: MagicMock,
+    mock_setup_entry: AsyncMock,
+) -> None:
+    """Test the zeroconf flow."""
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": SOURCE_ZEROCONF}, data=DISCOVERY_INFO
+    )
+
+    assert result["description_placeholders"] == {"host": MOCK_HOST}
+    assert result["type"] is FlowResultType.FORM
+    assert result["step_id"] == "confirm_discovery"
+
+    progress = hass.config_entries.flow.async_progress()
+    assert len(progress) == 1
+    assert progress[0]["flow_id"] == result["flow_id"]
+    assert progress[0]["context"]["confirm_only"] is True
+
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"], user_input={}
+    )
+
+    assert result2["type"] is FlowResultType.CREATE_ENTRY
+    assert result2["context"]["source"] == "zeroconf"
+    assert result2["context"]["unique_id"] == "aa:bb:cc:dd:ee:ff"
+    assert result2["title"] == "SLZB-06p7"
+    assert result2["data"] == {
+        CONF_HOST: MOCK_HOST,
+    }
+
+    assert len(mock_setup_entry.mock_calls) == 1
+    assert len(mock_smlight_client.get_info.mock_calls) == 1
+
+
+async def test_zeroconf_flow_auth(
+    hass: HomeAssistant,
+    mock_smlight_client: MagicMock,
+    mock_setup_entry: AsyncMock,
+) -> None:
+    """Test the full zeroconf flow including authentication."""
+    mock_smlight_client.check_auth_needed.return_value = True
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": SOURCE_ZEROCONF}, data=DISCOVERY_INFO
+    )
+
+    assert result["description_placeholders"] == {"host": MOCK_HOST}
+    assert result["type"] is FlowResultType.FORM
+    assert result["step_id"] == "confirm_discovery"
+
+    progress = hass.config_entries.flow.async_progress()
+    assert len(progress) == 1
+    assert progress[0]["flow_id"] == result["flow_id"]
+    assert progress[0]["context"]["confirm_only"] is True
+
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"], user_input={}
+    )
+
+    assert result2["type"] is FlowResultType.FORM
+    assert result2["step_id"] == "auth"
+
+    progress2 = hass.config_entries.flow.async_progress()
+    assert len(progress2) == 1
+    assert progress2[0]["flow_id"] == result["flow_id"]
+
+    result3 = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        user_input={
+            CONF_USERNAME: MOCK_USERNAME,
+            CONF_PASSWORD: MOCK_PASSWORD,
+        },
+    )
+
+    assert result3["type"] is FlowResultType.CREATE_ENTRY
+    assert result3["context"]["source"] == "zeroconf"
+    assert result3["context"]["unique_id"] == "aa:bb:cc:dd:ee:ff"
+    assert result3["title"] == "SLZB-06p7"
+    assert result3["data"] == {
+        CONF_USERNAME: MOCK_USERNAME,
+        CONF_PASSWORD: MOCK_PASSWORD,
+        CONF_HOST: MOCK_HOST,
+    }
+
+    assert len(mock_setup_entry.mock_calls) == 1
+    assert len(mock_smlight_client.get_info.mock_calls) == 1
+
+
+@pytest.mark.usefixtures("mock_smlight_client")
+async def test_user_device_exists_abort(
+    hass: HomeAssistant, mock_config_entry: MockConfigEntry
+) -> None:
+    """Test we abort user flow if device already configured."""
+    mock_config_entry.add_to_hass(hass)
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN,
+        context={"source": SOURCE_USER},
+        data={
+            CONF_HOST: MOCK_HOST,
+        },
+    )
+
+    assert result["type"] is FlowResultType.ABORT
+    assert result["reason"] == "already_configured"
+
+
+@pytest.mark.usefixtures("mock_smlight_client")
+async def test_zeroconf_device_exists_abort(
+    hass: HomeAssistant, mock_config_entry: MockConfigEntry
+) -> None:
+    """Test we abort zeroconf flow if device already configured."""
+    mock_config_entry.add_to_hass(hass)
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN,
+        context={"source": SOURCE_ZEROCONF},
+        data=DISCOVERY_INFO,
+    )
+
+    assert result["type"] is FlowResultType.ABORT
+    assert result["reason"] == "already_configured"
+
+
+async def test_user_invalid_auth(
+    hass: HomeAssistant, mock_smlight_client: MagicMock, mock_setup_entry: AsyncMock
+) -> None:
+    """Test we handle invalid auth."""
+    mock_smlight_client.check_auth_needed.return_value = True
+    mock_smlight_client.authenticate.side_effect = SmlightAuthError
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN,
+        context={"source": SOURCE_USER},
+        data={
+            CONF_HOST: MOCK_HOST,
+        },
+    )
+
+    assert result["type"] is FlowResultType.FORM
+    assert result["step_id"] == "auth"
+
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        {
+            CONF_USERNAME: "test",
+            CONF_PASSWORD: "bad",
+        },
+    )
+
+    assert result2["type"] is FlowResultType.FORM
+    assert result2["errors"] == {"base": "invalid_auth"}
+    assert result2["step_id"] == "auth"
+
+    mock_smlight_client.authenticate.side_effect = None
+
+    result3 = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        {
+            CONF_USERNAME: "test",
+            CONF_PASSWORD: "good",
+        },
+    )
+
+    assert result3["type"] is FlowResultType.CREATE_ENTRY
+    assert result3["title"] == "SLZB-06p7"
+    assert result3["data"] == {
+        CONF_HOST: MOCK_HOST,
+        CONF_USERNAME: "test",
+        CONF_PASSWORD: "good",
+    }
+
+    assert len(mock_setup_entry.mock_calls) == 1
+    assert len(mock_smlight_client.get_info.mock_calls) == 1
+
+
+async def test_user_cannot_connect(
+    hass: HomeAssistant, mock_smlight_client: MagicMock, mock_setup_entry: AsyncMock
+) -> None:
+    """Test we handle user cannot connect error."""
+    mock_smlight_client.check_auth_needed.side_effect = SmlightConnectionError
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": SOURCE_USER}
+    )
+
+    result = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        {
+            CONF_HOST: "unknown.local",
+        },
+    )
+
+    assert result["type"] is FlowResultType.FORM
+    assert result["errors"] == {"base": "cannot_connect"}
+    assert result["step_id"] == "user"
+
+    mock_smlight_client.check_auth_needed.side_effect = None
+
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        {
+            CONF_HOST: MOCK_HOST,
+        },
+    )
+
+    assert result2["type"] is FlowResultType.CREATE_ENTRY
+    assert result2["title"] == "SLZB-06p7"
+
+    assert len(mock_setup_entry.mock_calls) == 1
+    assert len(mock_smlight_client.get_info.mock_calls) == 1
+
+
+async def test_auth_cannot_connect(
+    hass: HomeAssistant, mock_smlight_client: MagicMock
+) -> None:
+    """Test we abort auth step on cannot connect error."""
+    mock_smlight_client.check_auth_needed.return_value = True
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN, context={"source": SOURCE_USER}
+    )
+
+    result = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        {
+            CONF_HOST: MOCK_HOST,
+        },
+    )
+
+    assert result["type"] is FlowResultType.FORM
+    assert result["step_id"] == "auth"
+
+    mock_smlight_client.check_auth_needed.side_effect = SmlightConnectionError
+
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        {
+            CONF_USERNAME: MOCK_USERNAME,
+            CONF_PASSWORD: MOCK_PASSWORD,
+        },
+    )
+
+    assert result2["type"] is FlowResultType.ABORT
+    assert result2["reason"] == "cannot_connect"
+
+
+async def test_zeroconf_cannot_connect(
+    hass: HomeAssistant, mock_smlight_client: MagicMock
+) -> None:
+    """Test we abort flow on zeroconf cannot connect error."""
+    mock_smlight_client.check_auth_needed.side_effect = SmlightConnectionError
+
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN,
+        context={"source": SOURCE_ZEROCONF},
+        data=DISCOVERY_INFO,
+    )
+    assert result["type"] is FlowResultType.FORM
+    assert result["step_id"] == "confirm_discovery"
+
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        {},
+    )
+
+    assert result2["type"] is FlowResultType.ABORT
+    assert result2["reason"] == "cannot_connect"
+
+
+@pytest.mark.usefixtures("mock_smlight_client")
+async def test_zeroconf_legacy_mac(
+    hass: HomeAssistant, mock_smlight_client: MagicMock, mock_setup_entry: AsyncMock
+) -> None:
+    """Test we can get unique id MAC address for older firmwares."""
+    result = await hass.config_entries.flow.async_init(
+        DOMAIN,
+        context={"source": SOURCE_ZEROCONF},
+        data=DISCOVERY_INFO_LEGACY,
+    )
+
+    assert result["description_placeholders"] == {"host": MOCK_HOST}
+
+    result2 = await hass.config_entries.flow.async_configure(
+        result["flow_id"], user_input={}
+    )
+
+    assert result2["type"] is FlowResultType.CREATE_ENTRY
+    assert result2["context"]["source"] == "zeroconf"
+    assert result2["context"]["unique_id"] == "aa:bb:cc:dd:ee:ff"
+    assert result2["title"] == "SLZB-06p7"
+    assert result2["data"] == {
+        CONF_HOST: MOCK_HOST,
+    }
+
+    assert len(mock_setup_entry.mock_calls) == 1
+    assert len(mock_smlight_client.get_info.mock_calls) == 2
diff --git a/tests/components/smlight/test_init.py b/tests/components/smlight/test_init.py
new file mode 100644
index 00000000000..682993cb943
--- /dev/null
+++ b/tests/components/smlight/test_init.py
@@ -0,0 +1,94 @@
+"Test SMLIGHT SLZB device integration initialization."
+
+from unittest.mock import MagicMock
+
+from freezegun.api import FrozenDateTimeFactory
+from pysmlight.exceptions import SmlightAuthError, SmlightConnectionError
+import pytest
+from syrupy.assertion import SnapshotAssertion
+
+from homeassistant.components.smlight.const import SCAN_INTERVAL
+from homeassistant.config_entries import ConfigEntryState
+from homeassistant.const import STATE_UNAVAILABLE
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers import device_registry as dr
+
+from .conftest import setup_integration
+
+from tests.common import MockConfigEntry, async_fire_time_changed
+
+pytestmark = [
+    pytest.mark.usefixtures(
+        "mock_smlight_client",
+    )
+]
+
+
+async def test_async_setup_entry(
+    hass: HomeAssistant, mock_config_entry: MockConfigEntry
+) -> None:
+    """Test async_setup_entry."""
+    entry = await setup_integration(hass, mock_config_entry)
+
+    assert entry.state is ConfigEntryState.LOADED
+    assert entry.unique_id == "aa:bb:cc:dd:ee:ff"
+
+    assert await hass.config_entries.async_unload(entry.entry_id)
+    await hass.async_block_till_done()
+    assert entry.state is ConfigEntryState.NOT_LOADED
+
+
+async def test_async_setup_auth_failed(
+    hass: HomeAssistant,
+    mock_config_entry: MockConfigEntry,
+    mock_smlight_client: MagicMock,
+) -> None:
+    """Test async_setup_entry when authentication fails."""
+    mock_smlight_client.check_auth_needed.return_value = True
+    mock_smlight_client.authenticate.side_effect = SmlightAuthError
+    entry = await setup_integration(hass, mock_config_entry)
+
+    assert entry.state is ConfigEntryState.SETUP_ERROR
+
+    assert await hass.config_entries.async_unload(entry.entry_id)
+    await hass.async_block_till_done()
+    assert entry.state is ConfigEntryState.NOT_LOADED
+
+
+async def test_update_failed(
+    hass: HomeAssistant,
+    mock_config_entry: MockConfigEntry,
+    mock_smlight_client: MagicMock,
+    freezer: FrozenDateTimeFactory,
+) -> None:
+    """Test update failed due to connection error."""
+
+    await setup_integration(hass, mock_config_entry)
+    entity = hass.states.get("sensor.mock_title_core_chip_temp")
+    assert entity.state is not STATE_UNAVAILABLE
+
+    mock_smlight_client.get_info.side_effect = SmlightConnectionError
+
+    freezer.tick(SCAN_INTERVAL)
+    async_fire_time_changed(hass)
+    await hass.async_block_till_done()
+
+    entity = hass.states.get("sensor.mock_title_core_chip_temp")
+    assert entity is not None
+    assert entity.state == STATE_UNAVAILABLE
+
+
+async def test_device_info(
+    hass: HomeAssistant,
+    snapshot: SnapshotAssertion,
+    mock_config_entry: MockConfigEntry,
+    device_registry: dr.DeviceRegistry,
+) -> None:
+    """Test device registry information."""
+    entry = await setup_integration(hass, mock_config_entry)
+
+    device_entry = device_registry.async_get_device(
+        connections={(dr.CONNECTION_NETWORK_MAC, entry.unique_id)}
+    )
+    assert device_entry is not None
+    assert device_entry == snapshot
diff --git a/tests/components/smlight/test_sensor.py b/tests/components/smlight/test_sensor.py
new file mode 100644
index 00000000000..4d16a73a0a7
--- /dev/null
+++ b/tests/components/smlight/test_sensor.py
@@ -0,0 +1,54 @@
+"""Tests for the SMLIGHT sensor platform."""
+
+import pytest
+from syrupy.assertion import SnapshotAssertion
+
+from homeassistant.const import Platform
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers import device_registry as dr, entity_registry as er
+
+from .conftest import setup_integration
+
+from tests.common import MockConfigEntry, snapshot_platform
+
+pytestmark = [
+    pytest.mark.usefixtures(
+        "mock_smlight_client",
+    )
+]
+
+
+@pytest.fixture
+def platforms() -> Platform | list[Platform]:
+    """Platforms, which should be loaded during the test."""
+    return Platform.SENSOR
+
+
+@pytest.mark.usefixtures("entity_registry_enabled_by_default")
+async def test_sensors(
+    hass: HomeAssistant,
+    device_registry: dr.DeviceRegistry,
+    entity_registry: er.EntityRegistry,
+    mock_config_entry: MockConfigEntry,
+    snapshot: SnapshotAssertion,
+) -> None:
+    """Test the SMLIGHT sensors."""
+    entry = await setup_integration(hass, mock_config_entry)
+
+    await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)
+
+
+async def test_disabled_by_default_sensors(
+    hass: HomeAssistant,
+    entity_registry: er.EntityRegistry,
+    mock_config_entry: MockConfigEntry,
+) -> None:
+    """Test the disabled by default SMLIGHT sensors."""
+    await setup_integration(hass, mock_config_entry)
+
+    for sensor in ("ram_usage", "filesystem_usage"):
+        assert not hass.states.get(f"sensor.mock_title_{sensor}")
+
+        assert (entry := entity_registry.async_get(f"sensor.mock_title_{sensor}"))
+        assert entry.disabled
+        assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
-- 
GitLab