diff --git a/kasa/module.py b/kasa/module.py
index 5066c953..854ab960 100644
--- a/kasa/module.py
+++ b/kasa/module.py
@@ -37,7 +37,11 @@ class Module(ABC):
 
     def _add_feature(self, feature: Feature):
         """Add module feature."""
-        feat_name = f"{self._module}_{feature.name}"
+
+        def _slugified_name(name):
+            return name.lower().replace(" ", "_").replace("'", "_")
+
+        feat_name = _slugified_name(feature.name)
         if feat_name in self._module_features:
             raise KasaException("Duplicate name detected %s" % feat_name)
         self._module_features[feat_name] = feature
diff --git a/kasa/smart/modules/temperature.py b/kasa/smart/modules/temperature.py
index c33e565b..dbfe7c63 100644
--- a/kasa/smart/modules/temperature.py
+++ b/kasa/smart/modules/temperature.py
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
 class TemperatureSensor(SmartModule):
     """Implementation of temperature module."""
 
-    REQUIRED_COMPONENT = "humidity"
+    REQUIRED_COMPONENT = "temperature"
     QUERY_GETTER_NAME = "get_comfort_temp_config"
 
     def __init__(self, device: "SmartDevice", module: str):
@@ -53,7 +53,7 @@ class TemperatureSensor(SmartModule):
 
     @property
     def temperature_warning(self) -> bool:
-        """Return True if humidity is outside of the wanted range."""
+        """Return True if temperature is outside of the wanted range."""
         return self._device.sys_info["current_temp_exception"] != 0
 
     @property
diff --git a/kasa/tests/conftest.py b/kasa/tests/conftest.py
index 0917f081..bec48bde 100644
--- a/kasa/tests/conftest.py
+++ b/kasa/tests/conftest.py
@@ -2,7 +2,7 @@ import warnings
 from typing import Dict
 from unittest.mock import MagicMock
 
-import pytest  # type: ignore # see https://github.com/pytest-dev/pytest/issues/3342
+import pytest
 
 from kasa import (
     DeviceConfig,
diff --git a/kasa/tests/device_fixtures.py b/kasa/tests/device_fixtures.py
index 085bab8e..71cc34bd 100644
--- a/kasa/tests/device_fixtures.py
+++ b/kasa/tests/device_fixtures.py
@@ -1,3 +1,4 @@
+from itertools import chain
 from typing import Dict, List, Set
 
 import pytest
@@ -106,6 +107,7 @@ DIMMERS = {
 }
 
 HUBS_SMART = {"H100"}
+SENSORS_SMART = {"T315"}
 
 WITH_EMETER_IOT = {"HS110", "HS300", "KP115", "KP125", *BULBS_IOT}
 WITH_EMETER_SMART = {"P110", "KP125M", "EP25"}
@@ -121,6 +123,7 @@ ALL_DEVICES_SMART = (
     .union(STRIPS_SMART)
     .union(DIMMERS_SMART)
     .union(HUBS_SMART)
+    .union(SENSORS_SMART)
     .union(SWITCHES_SMART)
 )
 ALL_DEVICES = ALL_DEVICES_IOT.union(ALL_DEVICES_SMART)
@@ -263,6 +266,9 @@ dimmers_smart = parametrize(
 hubs_smart = parametrize(
     "hubs smart", model_filter=HUBS_SMART, protocol_filter={"SMART"}
 )
+sensors_smart = parametrize(
+    "sensors smart", model_filter=SENSORS_SMART, protocol_filter={"SMART.CHILD"}
+)
 device_smart = parametrize(
     "devices smart", model_filter=ALL_DEVICES_SMART, protocol_filter={"SMART"}
 )
@@ -283,6 +289,7 @@ def check_categories():
         + bulb_smart.args[1]
         + dimmers_smart.args[1]
         + hubs_smart.args[1]
+        + sensors_smart.args[1]
     )
     diffs: Set[FixtureInfo] = set(FIXTURE_DATA) - set(categorized_fixtures)
     if diffs:
@@ -299,24 +306,14 @@ check_categories()
 
 def device_for_fixture_name(model, protocol):
     if "SMART" in protocol:
-        for d in PLUGS_SMART:
+        for d in chain(
+            PLUGS_SMART, SWITCHES_SMART, STRIPS_SMART, HUBS_SMART, SENSORS_SMART
+        ):
             if d in model:
                 return SmartDevice
-        for d in SWITCHES_SMART:
-            if d in model:
-                return SmartDevice
-        for d in BULBS_SMART:
+        for d in chain(BULBS_SMART, DIMMERS_SMART):
             if d in model:
                 return SmartBulb
-        for d in DIMMERS_SMART:
-            if d in model:
-                return SmartBulb
-        for d in STRIPS_SMART:
-            if d in model:
-                return SmartDevice
-        for d in HUBS_SMART:
-            if d in model:
-                return SmartDevice
     else:
         for d in STRIPS_IOT:
             if d in model:
@@ -378,7 +375,8 @@ async def get_device_for_fixture(fixture_data: FixtureInfo):
         discovery_data = {
             "system": {"get_sysinfo": fixture_data.data["system"]["get_sysinfo"]}
         }
-    if discovery_data:  # Child devices do not have discovery info
+
+    if discovery_data: # Child devices do not have discovery info
         d.update_from_discover_info(discovery_data)
 
     await _update_and_close(d)
@@ -392,7 +390,7 @@ async def get_device_for_fixture_protocol(fixture, protocol):
             return await get_device_for_fixture(fixture_info)
 
 
-@pytest.fixture(params=FIXTURE_DATA, ids=idgenerator)
+@pytest.fixture(params=filter_fixtures("main devices"), ids=idgenerator)
 async def dev(request):
     """Device fixture.
 
diff --git a/kasa/tests/fixtureinfo.py b/kasa/tests/fixtureinfo.py
index 70d385f6..08414ad4 100644
--- a/kasa/tests/fixtureinfo.py
+++ b/kasa/tests/fixtureinfo.py
@@ -93,7 +93,7 @@ def filter_fixtures(
 
     data_root_filter: return fixtures containing the supplied top
         level key, i.e. discovery_result
-    protocol_filter: set of protocols to match, IOT or SMART
+    protocol_filter: set of protocols to match, IOT, SMART, SMART.CHILD
     model_filter: set of device models to match
     component_filter: filter SMART fixtures that have the provided
     component in component_nego details.
diff --git a/kasa/tests/fixtures/smart/child/.gitkeep b/kasa/tests/fixtures/smart/child/.gitkeep
deleted file mode 100644
index 74bef849..00000000
--- a/kasa/tests/fixtures/smart/child/.gitkeep
+++ /dev/null
@@ -1 +0,0 @@
-Can be deleted when first fixture is added
diff --git a/kasa/tests/fixtures/smart/child/T315(EU)_1.0_1.7.0.json b/kasa/tests/fixtures/smart/child/T315(EU)_1.0_1.7.0.json
new file mode 100644
index 00000000..4fc49b0e
--- /dev/null
+++ b/kasa/tests/fixtures/smart/child/T315(EU)_1.0_1.7.0.json
@@ -0,0 +1,537 @@
+{
+   "component_nego" : {
+      "component_list" : [
+         {
+            "id" : "device",
+            "ver_code" : 2
+         },
+         {
+            "id" : "quick_setup",
+            "ver_code" : 3
+         },
+         {
+            "id" : "trigger_log",
+            "ver_code" : 1
+         },
+         {
+            "id" : "time",
+            "ver_code" : 1
+         },
+         {
+            "id" : "device_local_time",
+            "ver_code" : 1
+         },
+         {
+            "id" : "account",
+            "ver_code" : 1
+         },
+         {
+            "id" : "synchronize",
+            "ver_code" : 1
+         },
+         {
+            "id" : "cloud_connect",
+            "ver_code" : 1
+         },
+         {
+            "id" : "iot_cloud",
+            "ver_code" : 1
+         },
+         {
+            "id" : "firmware",
+            "ver_code" : 1
+         },
+         {
+            "id" : "localSmart",
+            "ver_code" : 1
+         },
+         {
+            "id" : "battery_detect",
+            "ver_code" : 1
+         },
+         {
+            "id" : "temperature",
+            "ver_code" : 1
+         },
+         {
+            "id" : "humidity",
+            "ver_code" : 1
+         },
+         {
+            "id" : "temp_humidity_record",
+            "ver_code" : 1
+         },
+         {
+            "id" : "comfort_temperature",
+            "ver_code" : 1
+         },
+         {
+            "id" : "comfort_humidity",
+            "ver_code" : 1
+         },
+         {
+            "id" : "report_mode",
+            "ver_code" : 1
+         }
+      ]
+   },
+   "get_connect_cloud_state" : {
+      "status" : 0
+   },
+   "get_device_info" : {
+      "at_low_battery" : false,
+      "avatar" : "",
+      "battery_percentage" : 100,
+      "bind_count" : 1,
+      "category" : "subg.trigger.temp-hmdt-sensor",
+      "current_humidity" : 61,
+      "current_humidity_exception" : 1,
+      "current_temp" : 21.4,
+      "current_temp_exception" : 0,
+      "device_id" : "SCRUBBED_CHILD_DEVICE_ID_1",
+      "fw_ver" : "1.7.0 Build 230424 Rel.170332",
+      "hw_id" : "00000000000000000000000000000000",
+      "hw_ver" : "1.0",
+      "jamming_rssi" : -122,
+      "jamming_signal_level" : 1,
+      "lastOnboardingTimestamp" : 1706990901,
+      "mac" : "F0A731000000",
+      "model" : "T315",
+      "nickname" : "I01BU0tFRF9OQU1FIw==",
+      "oem_id" : "00000000000000000000000000000000",
+      "parent_device_id" : "0000000000000000000000000000000000000000",
+      "region" : "Europe/Berlin",
+      "report_interval" : 16,
+      "rssi" : -56,
+      "signal_level" : 3,
+      "specs" : "EU",
+      "status" : "online",
+      "status_follow_edge" : false,
+      "temp_unit" : "celsius",
+      "type" : "SMART.TAPOSENSOR"
+   },
+   "get_fw_download_state" : {
+      "cloud_cache_seconds" : 1,
+      "download_progress" : 0,
+      "reboot_time" : 5,
+      "status" : 0,
+      "upgrade_time" : 5
+   },
+   "get_latest_fw" : {
+      "fw_ver" : "1.8.0 Build 230921 Rel.091446",
+      "hw_id" : "00000000000000000000000000000000",
+      "need_to_upgrade" : true,
+      "oem_id" : "00000000000000000000000000000000",
+      "release_date" : "2023-12-01",
+      "release_note" : "Modifications and Bug Fixes:\nEnhance the stability of the sensor.",
+      "type" : 2
+   },
+   "get_temp_humidity_records" : {
+      "local_time" : 1709061516,
+      "past24h_humidity" : [
+         60,
+         60,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         58,
+         59,
+         59,
+         58,
+         59,
+         59,
+         59,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         60,
+         60,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         59,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         60,
+         64,
+         56,
+         53,
+         55,
+         56,
+         57,
+         57,
+         58,
+         59,
+         63,
+         63,
+         62,
+         62,
+         62,
+         62,
+         61,
+         62,
+         62,
+         61,
+         61
+      ],
+      "past24h_humidity_exception" : [
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         4,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         3,
+         3,
+         2,
+         2,
+         2,
+         2,
+         1,
+         2,
+         2,
+         1,
+         1
+      ],
+      "past24h_temp" : [
+         217,
+         216,
+         215,
+         214,
+         214,
+         214,
+         214,
+         214,
+         214,
+         213,
+         213,
+         213,
+         213,
+         213,
+         212,
+         212,
+         211,
+         211,
+         211,
+         211,
+         211,
+         211,
+         212,
+         212,
+         212,
+         211,
+         211,
+         211,
+         211,
+         212,
+         212,
+         212,
+         212,
+         212,
+         211,
+         211,
+         211,
+         212,
+         213,
+         214,
+         214,
+         214,
+         213,
+         212,
+         212,
+         212,
+         212,
+         212,
+         212,
+         212,
+         212,
+         212,
+         212,
+         213,
+         213,
+         213,
+         213,
+         213,
+         213,
+         213,
+         213,
+         213,
+         213,
+         214,
+         214,
+         215,
+         215,
+         215,
+         214,
+         215,
+         216,
+         216,
+         216,
+         216,
+         216,
+         216,
+         216,
+         205,
+         196,
+         210,
+         213,
+         213,
+         213,
+         213,
+         213,
+         214,
+         215,
+         214,
+         214,
+         213,
+         213,
+         214,
+         214,
+         214,
+         213,
+         213
+      ],
+      "past24h_temp_exception" : [
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         -4,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0,
+         0
+      ],
+      "temp_unit" : "celsius"
+   },
+   "get_trigger_logs" : {
+      "logs" : [
+         {
+            "event" : "tooDry",
+            "eventId" : "118040a8-5422-1100-0804-0a8542211000",
+            "id" : 1,
+            "timestamp" : 1706996915
+         }
+      ],
+      "start_id" : 1,
+      "sum" : 1
+   }
+}
diff --git a/kasa/tests/smart/__init__.py b/kasa/tests/smart/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/kasa/tests/smart/modules/__init__.py b/kasa/tests/smart/modules/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/kasa/tests/smart/modules/test_humidity.py b/kasa/tests/smart/modules/test_humidity.py
new file mode 100644
index 00000000..99e4702e
--- /dev/null
+++ b/kasa/tests/smart/modules/test_humidity.py
@@ -0,0 +1,26 @@
+import pytest
+
+from kasa.smart.modules import HumiditySensor
+from kasa.tests.device_fixtures import parametrize
+
+humidity = parametrize("has humidity", component_filter="humidity", protocol_filter={"SMART.CHILD"})
+
+
+@humidity
+@pytest.mark.parametrize(
+    "feature, type",
+    [
+        ("humidity", int),
+        ("humidity_warning", bool),
+    ],
+)
+async def test_humidity_features(dev, feature, type):
+    """Test that features are registered and work as expected."""
+    humidity: HumiditySensor = dev.modules["HumiditySensor"]
+
+    prop = getattr(humidity, feature)
+    assert isinstance(prop, type)
+
+    feat = humidity._module_features[feature]
+    assert feat.value == prop
+    assert isinstance(feat.value, type)
diff --git a/kasa/tests/smart/modules/test_temperature.py b/kasa/tests/smart/modules/test_temperature.py
new file mode 100644
index 00000000..649b5bc4
--- /dev/null
+++ b/kasa/tests/smart/modules/test_temperature.py
@@ -0,0 +1,27 @@
+import pytest
+
+from kasa.smart.modules import TemperatureSensor
+from kasa.tests.device_fixtures import parametrize
+
+temperature = parametrize("has temperature", component_filter="temperature", protocol_filter={"SMART.CHILD"})
+
+
+@temperature
+@pytest.mark.parametrize(
+    "feature, type",
+    [
+        ("temperature", float),
+        ("temperature_warning", bool),
+        ("temperature_unit", str),
+    ],
+)
+async def test_temperature_features(dev, feature, type):
+    """Test that features are registered and work as expected."""
+    temp_module: TemperatureSensor = dev.modules["TemperatureSensor"]
+
+    prop = getattr(temp_module, feature)
+    assert isinstance(prop, type)
+
+    feat = temp_module._module_features[feature]
+    assert feat.value == prop
+    assert isinstance(feat.value, type)