diff --git a/README.md b/README.md
index fcc28190..2533b908 100644
--- a/README.md
+++ b/README.md
@@ -194,7 +194,7 @@ The following devices have been tested and confirmed as working. If your device
- **Bulbs**: L510B, L510E, L530E
- **Light Strips**: L900-10, L900-5, L920-5, L930-5
- **Hubs**: H100
-- **Hub-Connected Devices\*\*\***: T110, T300, T310, T315
+- **Hub-Connected Devices\*\*\***: T100, T110, T300, T310, T315
\* Model requires authentication
diff --git a/SUPPORTED.md b/SUPPORTED.md
index a0d301b3..5e6e8553 100644
--- a/SUPPORTED.md
+++ b/SUPPORTED.md
@@ -231,6 +231,8 @@ All Tapo devices require authentication.
Hub-Connected Devices may work acros
### Hub-Connected Devices
+- **T100**
+ - Hardware: 1.0 (EU) / Firmware: 1.12.0
- **T110**
- Hardware: 1.0 (EU) / Firmware: 1.8.0
- **T300**
diff --git a/kasa/module.py b/kasa/module.py
index 69c4e9e2..fe370603 100644
--- a/kasa/module.py
+++ b/kasa/module.py
@@ -111,6 +111,7 @@ class Module(ABC):
LightTransition: Final[ModuleName[smart.LightTransition]] = ModuleName(
"LightTransition"
)
+ MotionSensor: Final[ModuleName[smart.MotionSensor]] = ModuleName("MotionSensor")
ReportMode: Final[ModuleName[smart.ReportMode]] = ModuleName("ReportMode")
SmartLightEffect: Final[ModuleName[smart.SmartLightEffect]] = ModuleName(
"LightEffect"
diff --git a/kasa/smart/modules/__init__.py b/kasa/smart/modules/__init__.py
index fd987751..24d5749e 100644
--- a/kasa/smart/modules/__init__.py
+++ b/kasa/smart/modules/__init__.py
@@ -22,6 +22,7 @@ from .lighteffect import LightEffect
from .lightpreset import LightPreset
from .lightstripeffect import LightStripEffect
from .lighttransition import LightTransition
+from .motionsensor import MotionSensor
from .reportmode import ReportMode
from .temperaturecontrol import TemperatureControl
from .temperaturesensor import TemperatureSensor
@@ -54,6 +55,7 @@ __all__ = [
"Color",
"WaterleakSensor",
"ContactSensor",
+ "MotionSensor",
"FrostProtection",
"SmartLightEffect",
]
diff --git a/kasa/smart/modules/motionsensor.py b/kasa/smart/modules/motionsensor.py
new file mode 100644
index 00000000..169b25b6
--- /dev/null
+++ b/kasa/smart/modules/motionsensor.py
@@ -0,0 +1,36 @@
+"""Implementation of motion sensor module."""
+
+from __future__ import annotations
+
+from ...feature import Feature
+from ..smartmodule import SmartModule
+
+
+class MotionSensor(SmartModule):
+ """Implementation of motion sensor module."""
+
+ REQUIRED_COMPONENT = "sensitivity"
+
+ def _initialize_features(self):
+ """Initialize features."""
+ self._add_feature(
+ Feature(
+ self._device,
+ id="motion_detected",
+ name="Motion detected",
+ container=self,
+ attribute_getter="motion_detected",
+ icon="mdi:motion-sensor",
+ category=Feature.Category.Primary,
+ type=Feature.Type.BinarySensor,
+ )
+ )
+
+ def query(self) -> dict:
+ """Query to execute during the update cycle."""
+ return {}
+
+ @property
+ def motion_detected(self):
+ """Return True if the motion has been detected."""
+ return self._device.sys_info["detected"]
diff --git a/kasa/tests/device_fixtures.py b/kasa/tests/device_fixtures.py
index 1eb3e829..fca5960a 100644
--- a/kasa/tests/device_fixtures.py
+++ b/kasa/tests/device_fixtures.py
@@ -117,7 +117,7 @@ DIMMERS = {
}
HUBS_SMART = {"H100", "KH100"}
-SENSORS_SMART = {"T310", "T315", "T300", "T110"}
+SENSORS_SMART = {"T310", "T315", "T300", "T100", "T110"}
THERMOSTATS_SMART = {"KE100"}
WITH_EMETER_IOT = {"HS110", "HS300", "KP115", "KP125", *BULBS_IOT}
diff --git a/kasa/tests/fixtures/smart/child/T100(EU)_1.0_1.12.0.json b/kasa/tests/fixtures/smart/child/T100(EU)_1.0_1.12.0.json
new file mode 100644
index 00000000..00e46787
--- /dev/null
+++ b/kasa/tests/fixtures/smart/child/T100(EU)_1.0_1.12.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": "sensitivity",
+ "ver_code": 1
+ }
+ ]
+ },
+ "get_connect_cloud_state": {
+ "status": 0
+ },
+ "get_device_info": {
+ "at_low_battery": false,
+ "avatar": "sensor",
+ "bind_count": 1,
+ "category": "subg.trigger.motion-sensor",
+ "detected": false,
+ "device_id": "SCRUBBED_CHILD_DEVICE_ID_3",
+ "fw_ver": "1.12.0 Build 230512 Rel.103011",
+ "hw_id": "00000000000000000000000000000000",
+ "hw_ver": "1.0",
+ "jamming_rssi": -118,
+ "jamming_signal_level": 1,
+ "lastOnboardingTimestamp": 1703860126,
+ "mac": "E4FAC4000000",
+ "model": "T100",
+ "nickname": "I01BU0tFRF9OQU1FIw==",
+ "oem_id": "00000000000000000000000000000000",
+ "parent_device_id": "0000000000000000000000000000000000000000",
+ "region": "Europe/Berlin",
+ "report_interval": 60,
+ "rssi": -73,
+ "signal_level": 2,
+ "specs": "EU",
+ "status": "online",
+ "status_follow_edge": false,
+ "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_size": 0,
+ "fw_ver": "1.12.0 Build 230512 Rel.103011",
+ "hw_id": "",
+ "need_to_upgrade": false,
+ "oem_id": "",
+ "release_date": "",
+ "release_note": "",
+ "type": 0
+ },
+ "get_temp_humidity_records": {
+ "local_time": 1721645923,
+ "past24h_humidity": [
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000
+ ],
+ "past24h_humidity_exception": [
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000
+ ],
+ "past24h_temp": [
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000
+ ],
+ "past24h_temp_exception": [
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000,
+ -1000
+ ],
+ "temp_unit": "celsius"
+ },
+ "get_trigger_logs": {
+ "logs": [
+ {
+ "event": "motion",
+ "eventId": "f883b62c-e18f-30ef-883b-62ce18f30ef8",
+ "id": 28763,
+ "timestamp": 1721643865
+ },
+ {
+ "event": "motion",
+ "eventId": "c5157545-55d5-157d-4157-54555d5157d4",
+ "id": 28748,
+ "timestamp": 1721630821
+ },
+ {
+ "event": "motion",
+ "eventId": "1b587961-edab-08d1-b587-961edab08d1b",
+ "id": 28746,
+ "timestamp": 1721629441
+ },
+ {
+ "event": "motion",
+ "eventId": "8ac5e271-3894-c269-bc5e-2713894c269b",
+ "id": 28738,
+ "timestamp": 1721622777
+ },
+ {
+ "event": "motion",
+ "eventId": "1ef8037e-c097-bc21-ef80-37ec097bc21e",
+ "id": 28722,
+ "timestamp": 1721596432
+ }
+ ],
+ "start_id": 28763,
+ "sum": 86
+ }
+}
diff --git a/kasa/tests/smart/modules/test_motionsensor.py b/kasa/tests/smart/modules/test_motionsensor.py
new file mode 100644
index 00000000..59fbef68
--- /dev/null
+++ b/kasa/tests/smart/modules/test_motionsensor.py
@@ -0,0 +1,28 @@
+import pytest
+
+from kasa import Module, SmartDevice
+from kasa.tests.device_fixtures import parametrize
+
+motion = parametrize(
+ "is motion sensor", model_filter="T100", protocol_filter={"SMART.CHILD"}
+)
+
+
+@motion
+@pytest.mark.parametrize(
+ "feature, type",
+ [
+ ("motion_detected", bool),
+ ],
+)
+async def test_motion_features(dev: SmartDevice, feature, type):
+ """Test that features are registered and work as expected."""
+ motion = dev.modules.get(Module.MotionSensor)
+ assert motion is not None
+
+ prop = getattr(motion, feature)
+ assert isinstance(prop, type)
+
+ feat = dev.features[feature]
+ assert feat.value == prop
+ assert isinstance(feat.value, type)