diff -pruN 0.5.4-1/.github/workflows/publish-to-pypi.yml 0.5.8-1/.github/workflows/publish-to-pypi.yml
--- 0.5.4-1/.github/workflows/publish-to-pypi.yml	2025-05-29 08:43:20.000000000 +0000
+++ 0.5.8-1/.github/workflows/publish-to-pypi.yml	2025-08-12 01:53:19.000000000 +0000
@@ -9,17 +9,17 @@ jobs:
     name: Builds and publishes releases to PyPI
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v3.0.2
-      - name: Set up Python 3.9
-        uses: actions/setup-python@v3.1.2
+      - uses: actions/checkout@v4
+      - name: Set up Python 3.13
+        uses: actions/setup-python@v5
         with:
-          python-version: 3.9
-      - name: Install wheel
+          python-version: 3.13
+      - name: Install build
         run: >-
-          pip install wheel==0.45.1
+          pip install build
       - name: Build
         run: >-
-          python3 setup.py sdist bdist_wheel
+          python3 -m build
       - name: Publish release to PyPI
         uses: pypa/gh-action-pypi-publish@v1.5.0
         with:
diff -pruN 0.5.4-1/README.md 0.5.8-1/README.md
--- 0.5.4-1/README.md	2025-05-29 08:43:20.000000000 +0000
+++ 0.5.8-1/README.md	2025-08-12 01:53:19.000000000 +0000
@@ -55,4 +55,10 @@
 - YS5006-UC (FlowSmart Control)
 - YS5007-UC (FlowSmart Meter)
 - YS5008-UC (FlowSmart All-in-One)
-- YS8017-UC (Thermometer)  
+- YS8017-UC (Thermometer)
+- YS5009-UC (LeakStop Controller)
+- YS5029-UC (LeakStop Controller 2 Channel)
+- YS8009-UC (Soil Temperature & Humidity Sensor)
+- YS4102-UC (Smart Sprinkler Controller)
+- YS4103-UC (Smart Sprinkler Controller V2)
+- YS7A12-UC (Smoke Alarm)
diff -pruN 0.5.4-1/debian/changelog 0.5.8-1/debian/changelog
--- 0.5.4-1/debian/changelog	2025-05-31 13:22:32.000000000 +0000
+++ 0.5.8-1/debian/changelog	2025-08-13 14:08:03.000000000 +0000
@@ -1,3 +1,22 @@
+python-yolink-api (0.5.8-1) unstable; urgency=medium
+
+  * New upstream release.
+  * Upstream now needs setuptools version 77 or later.
+
+ -- Edward Betts <edward@4angle.com>  Wed, 13 Aug 2025 15:08:03 +0100
+
+python-yolink-api (0.5.7-1) experimental; urgency=medium
+
+  * New upstream release.
+
+ -- Edward Betts <edward@4angle.com>  Sat, 19 Jul 2025 15:58:32 +0200
+
+python-yolink-api (0.5.5-1) unstable; urgency=medium
+
+  * New upstream release.
+
+ -- Edward Betts <edward@4angle.com>  Mon, 30 Jun 2025 19:39:58 +0100
+
 python-yolink-api (0.5.4-1) unstable; urgency=medium
 
   * New upstream release.
diff -pruN 0.5.4-1/debian/control 0.5.8-1/debian/control
--- 0.5.4-1/debian/control	2025-05-31 13:21:27.000000000 +0000
+++ 0.5.8-1/debian/control	2025-08-13 14:08:03.000000000 +0000
@@ -9,7 +9,7 @@ Build-Depends:
  dh-sequence-python3,
  pybuild-plugin-pyproject,
  python3-all,
- python3-setuptools,
+ python3-setuptools (>= 77.0),
 Rules-Requires-Root: no
 Standards-Version: 4.7.2
 Homepage: https://github.com/YoSmart-Inc/yolink-api
diff -pruN 0.5.4-1/pyproject.toml 0.5.8-1/pyproject.toml
--- 0.5.4-1/pyproject.toml	2025-05-29 08:43:20.000000000 +0000
+++ 0.5.8-1/pyproject.toml	2025-08-12 01:53:19.000000000 +0000
@@ -1,6 +1,31 @@
 [build-system]
-requires = [
-    "setuptools>=42",
-    "wheel"
+requires = ["setuptools>=77.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "yolink-api"
+version = "0.5.8"
+license = "MIT"
+license-files = ["LICENSE"]
+description = "A library to authenticate with yolink device"
+readme = "README.md"
+authors = [{ name = "YoSmart" }]
+requires-python = ">=3.9"
+classifiers = [
+  "Programming Language :: Python :: 3",
+  "Operating System :: OS Independent",
 ]
-build-backend = "setuptools.build_meta"
\ No newline at end of file
+keywords = ["yolink", "api"]
+dependencies = [
+  "aiohttp>=3.8.1",
+  "aiomqtt>=2.0.0,<3.0.0",
+  "pydantic>=2.0.0",
+  "tenacity>=8.1.0",
+]
+
+[project.urls]
+"Source" = "https://github.com/YoSmart-Inc/yolink-api"
+"Bug Tracker" = "https://github.com/YoSmart-Inc/yolink-api/issues"
+
+[tool.setuptools.packages.find]
+include = ["yolink*"]
diff -pruN 0.5.4-1/requirements.txt 0.5.8-1/requirements.txt
--- 0.5.4-1/requirements.txt	2025-05-29 08:43:20.000000000 +0000
+++ 0.5.8-1/requirements.txt	1970-01-01 00:00:00.000000000 +0000
@@ -1,5 +0,0 @@
-setuptools==58.1.0
-aiohttp>=3.8.1
-pydantic>=1.9.0
-aiomqtt>=2.0.0
-tenacity>=8.1.0
diff -pruN 0.5.4-1/setup.py 0.5.8-1/setup.py
--- 0.5.4-1/setup.py	2025-05-29 08:43:20.000000000 +0000
+++ 0.5.8-1/setup.py	1970-01-01 00:00:00.000000000 +0000
@@ -1,31 +0,0 @@
-#!/usr/bin/env python
-from setuptools import setup
-
-setup(
-    name="yolink-api",
-    version="0.5.4",
-    author="YoSmart",
-    description="A library to authenticate with yolink device",
-    long_description=open("README.md").read(),
-    long_description_content_type="text/markdown",
-    url="https://github.com/YoSmart-Inc/yolink-api",
-    project_urls={
-        "Bug Tracker": "https://github.com/YoSmart-Inc/yolink-api/issues",
-    },
-    license="MIT",
-    keywords="yolink api",
-    packages=["yolink"],
-    zip_safe=False,
-    classifiers=[
-        "Programming Language :: Python :: 3",
-        "License :: OSI Approved :: MIT License",
-        "Operating System :: OS Independent",
-    ],
-    python_requires=">=3.6",
-    install_requires=[
-        "aiohttp>=3.8.1",
-        "aiomqtt>=2.0.0,<3.0.0",
-        "pydantic>=1.9.0",
-        "tenacity>=8.1.0",
-    ],
-)
diff -pruN 0.5.4-1/yolink/client.py 0.5.8-1/yolink/client.py
--- 0.5.4-1/yolink/client.py	2025-05-29 08:43:20.000000000 +0000
+++ 0.5.8-1/yolink/client.py	2025-08-12 01:53:19.000000000 +0000
@@ -67,7 +67,7 @@ class YoLinkClient:
             yl_resp = await self.post(url, json=bsdp, **kwargs)
             yl_resp.raise_for_status()
             _yl_body = await yl_resp.text()
-            brdp = BRDP.parse_raw(_yl_body)
+            brdp = BRDP.model_validate_json(_yl_body)
             brdp.check_response()
         except ClientError as client_err:
             raise YoLinkClientError(
diff -pruN 0.5.4-1/yolink/const.py 0.5.8-1/yolink/const.py
--- 0.5.4-1/yolink/const.py	2025-05-29 08:43:20.000000000 +0000
+++ 0.5.8-1/yolink/const.py	2025-08-12 01:53:19.000000000 +0000
@@ -16,6 +16,7 @@ ATTR_DEVICE_SERVICE_ZONE = "serviceZone"
 ATTR_DEVICE_MODEL_A = "A"
 ATTR_DEVICE_MODEL_C = "C"
 ATTR_DEVICE_MODEL_D = "D"
+ATTR_DEVICE_MODEL_HUB = "Hub"
 
 ATTR_DEVICE_DOOR_SENSOR = "DoorSensor"
 ATTR_DEVICE_TH_SENSOR = "THSensor"
@@ -44,5 +45,11 @@ ATTR_DEVICE_LOCK_V2 = "LockV2"
 ATTR_DEVICE_SOIL_TH_SENSOR = "SoilThcSensor"
 ATTR_DEVICE_SPRINKLER = "Sprinkler"
 ATTR_DEVICE_SPRINKLER_V2 = "SprinklerV2"
+ATTR_DEVICE_SMOKE_ALARM = "SmokeAlarm"
 
 UNIT_NOT_RECOGNIZED_TEMPLATE: Final = "{} is not a recognized {} unit."
+
+
+DEVICE_LEAK_STOP_MODELS = ["YS5009-UC", "YS5009-EC", "YS5029-UC", "YS5029-EC"]
+
+DEVICE_MODELS_SUPPORT_MODE_SWITCHING = DEVICE_LEAK_STOP_MODELS
diff -pruN 0.5.4-1/yolink/device.py 0.5.8-1/yolink/device.py
--- 0.5.4-1/yolink/device.py	2025-05-29 08:43:20.000000000 +0000
+++ 0.5.8-1/yolink/device.py	2025-08-12 01:53:19.000000000 +0000
@@ -4,16 +4,11 @@ from __future__ import annotations
 import abc
 from typing import Optional
 
+from pydantic import BaseModel, Field, field_validator
 from tenacity import RetryError
 
-try:
-    from pydantic.v1 import BaseModel, Field, validator
-except ImportError:
-    from pydantic import BaseModel, Field, validator
-
 from .client import YoLinkClient
 from .endpoint import Endpoint, Endpoints
-from .exception import YoLinkClientError
 from .model import BRDP, BSDPHelper
 from .const import (
     ATTR_DEVICE_ID,
@@ -23,16 +18,11 @@ from .const import (
     ATTR_DEVICE_MODEL_NAME,
     ATTR_DEVICE_PARENT_ID,
     ATTR_DEVICE_SERVICE_ZONE,
-    ATTR_DEVICE_WATER_DEPTH_SENSOR,
-    ATTR_DEVICE_WATER_METER_CONTROLLER,
-    ATTR_DEVICE_MULTI_WATER_METER_CONTROLLER,
+    DEVICE_MODELS_SUPPORT_MODE_SWITCHING,
 )
 from .client_request import ClientRequest
-from .message_resolver import (
-    water_depth_sensor_message_resolve,
-    water_meter_controller_message_resolve,
-    multi_water_meter_controller_message_resolve,
-)
+from .message_resolver import resolve_message
+from .device_helper import get_device_net_mode
 
 
 class YoLinkDeviceMode(BaseModel):
@@ -46,8 +36,9 @@ class YoLinkDeviceMode(BaseModel):
     device_parent_id: Optional[str] = Field(alias=ATTR_DEVICE_PARENT_ID)
     device_service_zone: Optional[str] = Field(alias=ATTR_DEVICE_SERVICE_ZONE)
 
-    @validator("device_parent_id")
-    def check_parent_id(cls, val):
+    @field_validator("device_parent_id")
+    @classmethod
+    def check_parent_id(cls, val: Optional[str]) -> Optional[str]:
         """Checking and replace parent id."""
         if val == "null":
             val = None
@@ -66,6 +57,8 @@ class YoLinkDevice(metaclass=abc.ABCMeta
         self.device_attrs: dict | None = None
         self.parent_id: str = device.device_parent_id
         self._client: YoLinkClient = client
+        self.class_mode: str = get_device_net_mode(device)
+        self._state: dict | None = {}
         if device.device_service_zone is not None:
             self.device_endpoint: Endpoint = (
                 Endpoints.EU.value
@@ -93,7 +86,7 @@ class YoLinkDevice(metaclass=abc.ABCMeta
                 url=self.device_endpoint.url, bsdp=bsdp_helper.build()
             )
         except RetryError as err:
-            raise YoLinkClientError("-1003", "yolink client request failed!") from err
+            raise err.last_attempt.result()
 
     async def get_state(self) -> BRDP:
         """Call *.getState with device to request realtime state data."""
@@ -101,19 +94,15 @@ class YoLinkDevice(metaclass=abc.ABCMeta
 
     async def fetch_state(self) -> BRDP:
         """Call *.fetchState with device to fetch state data."""
-        state_brdp: BRDP = await self.__invoke("fetchState", None)
-        if self.device_type == ATTR_DEVICE_WATER_DEPTH_SENSOR:
-            water_depth_sensor_message_resolve(
-                state_brdp.data.get("state"), self.device_attrs
-            )
-        if self.device_type == ATTR_DEVICE_WATER_METER_CONTROLLER:
-            water_meter_controller_message_resolve(
-                state_brdp.data.get("state"), self.device_model_name
-            )
-        if self.device_type == ATTR_DEVICE_MULTI_WATER_METER_CONTROLLER:
-            multi_water_meter_controller_message_resolve(
-                state_brdp.data.get("state"), self.device_model_name
+        if self.device_type in ["Hub", "SpeakerHub"]:
+            return BRDP(
+                code="000000",
+                desc="success",
+                method="fetchState",
+                data={},
             )
+        state_brdp: BRDP = await self.__invoke("fetchState", None)
+        resolve_message(self, state_brdp.data.get("state"), None)
         return state_brdp
 
     async def get_external_data(self) -> BRDP:
@@ -129,3 +118,7 @@ class YoLinkDevice(metaclass=abc.ABCMeta
         if self.parent_id is None or self.parent_id == "null":
             return None
         return self.parent_id
+
+    def is_support_mode_switching(self) -> bool:
+        """Check if the device supports mode switching."""
+        return self.device_model_name in DEVICE_MODELS_SUPPORT_MODE_SWITCHING
diff -pruN 0.5.4-1/yolink/device_helper.py 0.5.8-1/yolink/device_helper.py
--- 0.5.4-1/yolink/device_helper.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.5.8-1/yolink/device_helper.py	2025-08-12 01:53:19.000000000 +0000
@@ -0,0 +1,121 @@
+"""Helper functions for YoLink devices."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from .device import YoLinkDevice
+
+from .const import (
+    ATTR_DEVICE_LEAK_SENSOR,
+    ATTR_DEVICE_TH_SENSOR,
+    ATTR_DEVICE_DOOR_SENSOR,
+    ATTR_GARAGE_DOOR_CONTROLLER,
+    ATTR_DEVICE_DIMMER,
+    ATTR_DEVICE_FINGER,
+    ATTR_DEVICE_MANIPULATOR,
+    ATTR_DEVICE_CO_SMOKE_SENSOR,
+    ATTR_DEVICE_OUTLET,
+    ATTR_DEVICE_MULTI_OUTLET,
+    ATTR_DEVICE_SIREN,
+    ATTR_DEVICE_POWER_FAILURE_ALARM,
+    ATTR_DEVICE_MOTION_SENSOR,
+    ATTR_DEVICE_SWITCH,
+    ATTR_DEVICE_THERMOSTAT,
+    ATTR_DEVICE_SOIL_TH_SENSOR,
+    ATTR_DEVICE_LOCK,
+    ATTR_DEVICE_LOCK_V2,
+    ATTR_DEVICE_WATER_METER_CONTROLLER,
+    ATTR_DEVICE_VIBRATION_SENSOR,
+    ATTR_DEVICE_SMART_REMOTER,
+    ATTR_DEVICE_HUB,
+    ATTR_DEVICE_SPEAKER_HUB,
+    ATTR_DEVICE_WATER_DEPTH_SENSOR,
+    ATTR_DEVICE_MULTI_WATER_METER_CONTROLLER,
+    ATTR_DEVICE_SMOKE_ALARM,
+    ATTR_DEVICE_SPRINKLER,
+    ATTR_DEVICE_SPRINKLER_V2,
+)
+
+
+def get_device_net_mode(device: YoLinkDevice) -> str | None:
+    """Get device network mode."""
+    # Assuming all devices are WiFi for this example
+    device_type = device.device_type
+    device_model = device.device_model_name
+    device_short_model = None
+    if device_model is not None:
+        device_short_model = device_model.split("-")[0]
+    if device_type in [
+        ATTR_DEVICE_LEAK_SENSOR,
+        ATTR_DEVICE_DOOR_SENSOR,
+        ATTR_DEVICE_TH_SENSOR,
+        ATTR_DEVICE_MOTION_SENSOR,
+        ATTR_DEVICE_CO_SMOKE_SENSOR,
+        ATTR_DEVICE_POWER_FAILURE_ALARM,
+        ATTR_DEVICE_SOIL_TH_SENSOR,
+        ATTR_DEVICE_VIBRATION_SENSOR,
+        ATTR_DEVICE_SMART_REMOTER,
+        ATTR_DEVICE_WATER_DEPTH_SENSOR,
+        ATTR_DEVICE_SMOKE_ALARM,
+    ]:
+        if device_short_model in [
+            "YS7A02",
+            "YS8006",
+        ]:
+            return "D"
+        return "A"
+    if device_type in [
+        ATTR_DEVICE_MANIPULATOR,
+        ATTR_DEVICE_OUTLET,
+        ATTR_DEVICE_MULTI_OUTLET,
+        ATTR_DEVICE_THERMOSTAT,
+        ATTR_DEVICE_SIREN,
+        ATTR_DEVICE_SWITCH,
+        ATTR_GARAGE_DOOR_CONTROLLER,
+        ATTR_DEVICE_DIMMER,
+        ATTR_DEVICE_SPRINKLER,
+    ]:
+        if device_short_model in [
+            #
+            "YS4909",
+            # Mainpulator(Class D)
+            "YS5001",
+            "YS5002",
+            "YS5003",
+            "YS5012",
+            # Switch(Class D)
+            "YS5709",
+            # Siren(Class D)
+            "YS7104",
+            "YS7105",
+            "YS7107",
+        ]:
+            return "D"
+        return "C"
+    if device_type in [
+        ATTR_DEVICE_FINGER,
+        ATTR_DEVICE_LOCK,
+        ATTR_DEVICE_LOCK_V2,
+        ATTR_DEVICE_WATER_METER_CONTROLLER,
+        ATTR_DEVICE_MULTI_WATER_METER_CONTROLLER,
+        ATTR_DEVICE_SPRINKLER_V2,
+    ]:
+        if device_short_model in ["YS5007"]:
+            return "A"
+        return "D"
+    if device_type in [ATTR_DEVICE_HUB, ATTR_DEVICE_SPEAKER_HUB]:
+        return "Hub"
+    return None
+
+
+def get_device_keepalive_time(device: YoLinkDevice) -> int:
+    """Get device keepalive time in seconds."""
+    device_class_mode = get_device_net_mode(device)
+    if device_class_mode in ["A", "D"]:
+        return 32400
+    if device_class_mode == "C":
+        return 3600
+    if device_class_mode == "Hub":
+        return 600
diff -pruN 0.5.4-1/yolink/endpoint.py 0.5.8-1/yolink/endpoint.py
--- 0.5.4-1/yolink/endpoint.py	2025-05-29 08:43:20.000000000 +0000
+++ 0.5.8-1/yolink/endpoint.py	2025-08-12 01:53:19.000000000 +0000
@@ -14,17 +14,27 @@ class Endpoint:
     mqtt_broker_host: str
     mqtt_broker_port: int = 8003
 
-    def __init__(self, name: str, host: str):
+    def __init__(self, name: str, host: str, mqtt_host: str, mqtt_port: int):
         """Init SVR Endpoint."""
         self.name = name
         self.host = host
         self.url = f"https://{host}/open/yolink/v2/api"
-        self.mqtt_broker_host = host
-        self.mqtt_broker_port = 8003
+        self.mqtt_broker_host = mqtt_host
+        self.mqtt_broker_port = mqtt_port
 
 
 class Endpoints(Enum):
     """All YoLink SVR Endpoints."""
 
-    US: Endpoint = Endpoint(name="US", host="api.yosmart.com")
-    EU: Endpoint = Endpoint(name="EU", host="api-eu.yosmart.com")
+    US: Endpoint = Endpoint(
+        name="US",
+        host="api.yosmart.com",
+        mqtt_host="mqtt.api.yosmart.com",
+        mqtt_port=8003,
+    )
+    EU: Endpoint = Endpoint(
+        name="EU",
+        host="api-eu.yosmart.com",
+        mqtt_host="api-eu.yosmart.com",
+        mqtt_port=8003,
+    )
diff -pruN 0.5.4-1/yolink/message_resolver.py 0.5.8-1/yolink/message_resolver.py
--- 0.5.4-1/yolink/message_resolver.py	2025-05-29 08:43:20.000000000 +0000
+++ 0.5.8-1/yolink/message_resolver.py	2025-08-12 01:53:19.000000000 +0000
@@ -1,33 +1,43 @@
 """YoLink cloud message resolver."""
 
-from typing import Any
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
 from math import log2
 from decimal import Decimal, ROUND_DOWN
 
 from .unit_helper import UnitOfVolume, VolumeConverter
+from .const import (
+    ATTR_DEVICE_SMART_REMOTER,
+    ATTR_DEVICE_WATER_DEPTH_SENSOR,
+    ATTR_DEVICE_WATER_METER_CONTROLLER,
+    ATTR_DEVICE_MULTI_WATER_METER_CONTROLLER,
+    ATTR_DEVICE_SOIL_TH_SENSOR,
+    ATTR_DEVICE_SPRINKLER,
+    ATTR_DEVICE_SPRINKLER_V2,
+)
+
+if TYPE_CHECKING:
+    from .device import YoLinkDevice
 
 
-def smart_remoter_message_resolve(
-    event_type: str, msg_data: dict[str, Any]
-) -> dict[str, Any]:
+def smart_remoter_message_resolve(msg_data: dict[str, Any], event_type: str) -> None:
     """SmartRemoter message resolve."""
-    if msg_data is None:
-        return msg_data
-    btn_press_event = msg_data.get("event")
-    if btn_press_event is not None:
-        if event_type == "Report":
-            msg_data["event"] = None
-        else:
-            key_mask = btn_press_event["keyMask"]
-            button_sequence = 0 if key_mask == 0 else (int(log2(key_mask)) + 1)
-            # replace with button sequence
-            msg_data["event"]["keyMask"] = button_sequence
-    return msg_data
+    if msg_data is not None:
+        btn_press_event = msg_data.get("event")
+        if btn_press_event is not None:
+            if event_type == "Report":
+                msg_data["event"] = None
+            else:
+                key_mask = btn_press_event["keyMask"]
+                button_sequence = 0 if key_mask == 0 else (int(log2(key_mask)) + 1)
+                # replace with button sequence
+                msg_data["event"]["keyMask"] = button_sequence
 
 
 def water_depth_sensor_message_resolve(
     msg_data: dict[str, Any], dev_attrs: dict[str, Any]
-) -> dict[str, Any]:
+) -> None:
     """WaterDepthSensor message resolve."""
     if msg_data is not None:
         depth_value = msg_data.get("waterDepth")
@@ -42,124 +52,179 @@ def water_depth_sensor_message_resolve(
                 dev_range = range_attrs["range"]
                 dev_density = range_attrs["density"]
             msg_data["waterDepth"] = round(
-                (dev_range * (depth_value / 1000)) / dev_density, 2
+                (dev_range * (depth_value / 1000)) / dev_density, 3
             )
-    return msg_data
 
 
 def water_meter_controller_message_resolve(
     msg_data: dict[str, Any], device_model: str
-) -> dict[str, Any]:
+) -> None:
     """WaterMeterController message resolve."""
-    if msg_data is None:
-        return msg_data
-    if (meter_state := msg_data.get("state")) is None:
-        return msg_data
-    meter_step_factor: int = 10
-    # for some reason meter value can't be read
-    meter_value: int = meter_state.get("meter")
-    if meter_value is not None:
-        meter_unit = UnitOfVolume.GALLONS
-        if (meter_attrs := msg_data.get("attributes")) is not None:
-            if device_model.startswith("YS5009"):
-                meter_step_factor = (
-                    1 / (_meter_step_factor / (1000 * 100))
-                    if (_meter_step_factor := meter_attrs.get("meterStepFactor"))
-                    is not None
-                    else 10
+    if msg_data is not None and ((meter_state := msg_data.get("state")) is not None):
+        meter_step_factor: int = 10
+        # for some reason meter value can't be read
+        meter_value = meter_state.get("meter")
+        if meter_value is not None:
+            meter_unit = UnitOfVolume.GALLONS
+            if (meter_attrs := msg_data.get("attributes")) is not None:
+                if device_model.startswith("YS5009"):
+                    meter_step_factor = (
+                        1 / (_meter_step_factor / (1000 * 100))
+                        if (_meter_step_factor := meter_attrs.get("meterStepFactor"))
+                        is not None
+                        else 10
+                    )
+                else:
+                    meter_step_factor = (
+                        _meter_step_factor
+                        if (_meter_step_factor := meter_attrs.get("meterStepFactor"))
+                        is not None
+                        else 10
+                    )
+                meter_unit = (
+                    UnitOfVolume(_meter_unit)
+                    if (_meter_unit := meter_attrs.get("meterUnit")) is not None
+                    else UnitOfVolume.GALLONS
                 )
+            _meter_reading = None
+            if meter_step_factor < 0:
+                _meter_reading = meter_value * abs(meter_step_factor)
             else:
-                meter_step_factor = (
-                    _meter_step_factor
-                    if (_meter_step_factor := meter_attrs.get("meterStepFactor"))
-                    is not None
-                    else 10
-                )
-            meter_unit = (
-                UnitOfVolume(_meter_unit)
-                if (_meter_unit := meter_attrs.get("meterUnit")) is not None
-                else UnitOfVolume.GALLONS
-            )
-        _meter_reading = None
-        if meter_step_factor < 0:
-            _meter_reading = meter_value * abs(meter_step_factor)
-        else:
-            _meter_reading = meter_value / meter_step_factor
-        meter_value = VolumeConverter.convert(
-            _meter_reading, meter_unit, UnitOfVolume.CUBIC_METERS
-        )
-        msg_data["meter_reading"] = float(
-            Decimal(meter_value).quantize(Decimal(".00000"), rounding=ROUND_DOWN)
-        )
-    msg_data["valve_state"] = meter_state["valve"]
-    return msg_data
+                _meter_reading = meter_value / meter_step_factor
+            meter_value = VolumeConverter.convert(
+                _meter_reading, meter_unit, UnitOfVolume.CUBIC_METERS
+            )
+            msg_data["meter_reading"] = float(
+                Decimal(meter_value).quantize(Decimal(".00000"), rounding=ROUND_DOWN)
+            )
+        msg_data["valve_state"] = meter_state["valve"]
 
 
 def multi_water_meter_controller_message_resolve(
     msg_data: dict[str, Any],
     device_model: str,
-) -> dict[str, Any]:
-    if msg_data is None:
-        return msg_data
+) -> None:
     """MultiWaterMeterController message resolve."""
-    if (meter_state := msg_data.get("state")) is None:
-        return msg_data
-    meter_step_factor: int = 10
-    meter_reading_values: dict = meter_state.get("meters")
-    if meter_reading_values is not None:
-        meter_unit = UnitOfVolume.GALLONS
-        if (meter_attrs := msg_data.get("attributes")) is not None:
-            if device_model.startswith("YS5029"):
-                meter_step_factor = (
-                    1 / (_meter_step_factor / (1000 * 100))
-                    if (_meter_step_factor := meter_attrs.get("meterStepFactor"))
-                    is not None
-                    else 10
+    if msg_data is not None and ((meter_state := msg_data.get("state")) is not None):
+        meter_step_factor: int = 10
+        meter_reading_values: dict = meter_state.get("meters")
+        if meter_reading_values is not None:
+            meter_unit = UnitOfVolume.GALLONS
+            if (meter_attrs := msg_data.get("attributes")) is not None:
+                if device_model.startswith("YS5029"):
+                    meter_step_factor = (
+                        1 / (_meter_step_factor / (1000 * 100))
+                        if (_meter_step_factor := meter_attrs.get("meterStepFactor"))
+                        is not None
+                        else 10
+                    )
+                else:
+                    meter_step_factor = (
+                        _meter_step_factor
+                        if (_meter_step_factor := meter_attrs.get("meterStepFactor"))
+                        is not None
+                        else 10
+                    )
+                meter_unit = (
+                    UnitOfVolume(_meter_unit)
+                    if (_meter_unit := meter_attrs.get("meterUnit")) is not None
+                    else UnitOfVolume.GALLONS
                 )
+            _meter_1_reading = None
+            if meter_step_factor < 0:
+                _meter_1_reading = meter_reading_values["0"] * abs(meter_step_factor)
+            else:
+                _meter_1_reading = meter_reading_values["0"] / meter_step_factor
+            meter_reading_values["0"] = VolumeConverter.convert(
+                _meter_1_reading,
+                meter_unit,
+                UnitOfVolume.CUBIC_METERS,
+            )
+            _meter_2_reading = None
+            if meter_step_factor < 0:
+                _meter_2_reading = meter_reading_values["1"] * abs(meter_step_factor)
             else:
-                meter_step_factor = (
-                    _meter_step_factor
-                    if (_meter_step_factor := meter_attrs.get("meterStepFactor"))
-                    is not None
-                    else 10
+                _meter_2_reading = meter_reading_values["1"] / meter_step_factor
+            meter_reading_values["1"] = VolumeConverter.convert(
+                _meter_2_reading,
+                meter_unit,
+                UnitOfVolume.CUBIC_METERS,
+            )
+            msg_data["meter_1_reading"] = float(
+                Decimal(meter_reading_values["0"]).quantize(
+                    Decimal(".00000"), rounding=ROUND_DOWN
+                )
+            )
+            msg_data["meter_2_reading"] = float(
+                Decimal(meter_reading_values["1"]).quantize(
+                    Decimal(".00000"), rounding=ROUND_DOWN
                 )
-            meter_unit = (
-                UnitOfVolume(_meter_unit)
-                if (_meter_unit := meter_attrs.get("meterUnit")) is not None
-                else UnitOfVolume.GALLONS
-            )
-        _meter_1_reading = None
-        if meter_step_factor < 0:
-            _meter_1_reading = meter_reading_values["0"] * abs(meter_step_factor)
-        else:
-            _meter_1_reading = meter_reading_values["0"] / meter_step_factor
-        meter_reading_values["0"] = VolumeConverter.convert(
-            _meter_1_reading,
-            meter_unit,
-            UnitOfVolume.CUBIC_METERS,
-        )
-        _meter_2_reading = None
-        if meter_step_factor < 0:
-            _meter_2_reading = meter_reading_values["1"] * abs(meter_step_factor)
-        else:
-            _meter_2_reading = meter_reading_values["1"] / meter_step_factor
-        meter_reading_values["1"] = VolumeConverter.convert(
-            _meter_2_reading,
-            meter_unit,
-            UnitOfVolume.CUBIC_METERS,
-        )
-        msg_data["meter_1_reading"] = float(
-            Decimal(meter_reading_values["0"]).quantize(
-                Decimal(".00000"), rounding=ROUND_DOWN
-            )
-        )
-        msg_data["meter_2_reading"] = float(
-            Decimal(meter_reading_values["1"]).quantize(
-                Decimal(".00000"), rounding=ROUND_DOWN
-            )
-        )
-    # for some reason meter value can't be read
-    if (meter_valves := meter_state.get("valves")) is not None:
-        msg_data["valve_1_state"] = meter_valves["0"]
-        msg_data["valve_2_state"] = meter_valves["1"]
-    return msg_data
+            )
+        # for some reason meter value can't be read
+        if (meter_valves := meter_state.get("valves")) is not None:
+            msg_data["valve_1_state"] = meter_valves["0"]
+            msg_data["valve_2_state"] = meter_valves["1"]
+
+
+def soil_thc_sensor_message_resolve(
+    msg_data: dict[str, Any],
+) -> None:
+    """SoilThcSensor message resolve."""
+    if msg_data is not None and ((state := msg_data.get("state")) is not None):
+        msg_data["temperature"] = state.get("temperature")
+        msg_data["humidity"] = state.get("humidity")
+        msg_data["conductivity"] = state.get("conductivity")
+
+
+def sprinkler_message_resolve(
+    device: YoLinkDevice,
+    msg_data: dict[str, Any],
+    msg_type: str | None = None,
+) -> None:
+    """Sprinkler message resolve."""
+    if msg_data is not None:
+        if (state := msg_data.get("state")) is not None:
+            device._state = {"mode": state.get("mode")}
+            if (watering_data := state.get("watering")) is not None:
+                msg_data["valve"] = watering_data["left"] != watering_data["total"]
+        if msg_type == "waterReport":
+            if device._state is not None:
+                msg_data["state"] = {"mode": device._state.get("mode")}
+            if (event := msg_data.get("event")) is not None:
+                msg_data["valve"] = event == "start"
+
+
+def sprinkler_v2_message_resolve(
+    msg_data: dict[str, Any],
+) -> None:
+    """Sprinkler V2 message resolve."""
+    if msg_data is not None and ((state := msg_data.get("state")) is not None):
+        msg_data["valve"] = state.get("running")
+
+
+def resolve_message(
+    device: YoLinkDevice, msg_data: dict[str, Any], msg_type: str | None
+) -> None:
+    """Resolve device message."""
+    if device.device_type == ATTR_DEVICE_WATER_DEPTH_SENSOR:
+        water_depth_sensor_message_resolve(msg_data, device.device_attrs)
+    elif device.device_type == ATTR_DEVICE_WATER_METER_CONTROLLER:
+        water_meter_controller_message_resolve(msg_data, device.device_model_name)
+    elif device.device_type == ATTR_DEVICE_MULTI_WATER_METER_CONTROLLER:
+        multi_water_meter_controller_message_resolve(msg_data, device.device_model_name)
+    elif device.device_type == ATTR_DEVICE_SOIL_TH_SENSOR:
+        soil_thc_sensor_message_resolve(msg_data)
+    elif device.device_type == ATTR_DEVICE_SPRINKLER:
+        sprinkler_message_resolve(device, msg_data, msg_type)
+    elif device.device_type == ATTR_DEVICE_SPRINKLER_V2:
+        sprinkler_v2_message_resolve(msg_data)
+
+
+def resolve_sub_message(
+    device: YoLinkDevice, msg_data: dict[str, Any], msg_type: str
+) -> None:
+    """Resolve device pushing message."""
+    if device.device_type == ATTR_DEVICE_SMART_REMOTER:
+        smart_remoter_message_resolve(msg_data, msg_type)
+    else:
+        resolve_message(device, msg_data, msg_type)
diff -pruN 0.5.4-1/yolink/model.py 0.5.8-1/yolink/model.py
--- 0.5.4-1/yolink/model.py	2025-05-29 08:43:20.000000000 +0000
+++ 0.5.8-1/yolink/model.py	2025-08-12 01:53:19.000000000 +0000
@@ -2,10 +2,7 @@
 
 from typing import Any, Dict, Optional
 
-try:
-    from pydantic.v1 import BaseModel
-except ImportError:
-    from pydantic import BaseModel
+from pydantic import BaseModel
 
 from .exception import (
     YoLinkAuthFailError,
diff -pruN 0.5.4-1/yolink/mqtt_client.py 0.5.8-1/yolink/mqtt_client.py
--- 0.5.4-1/yolink/mqtt_client.py	2025-05-29 08:43:20.000000000 +0000
+++ 0.5.8-1/yolink/mqtt_client.py	2025-08-12 01:53:19.000000000 +0000
@@ -3,29 +3,15 @@
 import asyncio
 import logging
 from typing import Any
-import aiomqtt
 
-try:
-    from pydantic.v1 import ValidationError
-except ImportError:
-    from pydantic import ValidationError
+import aiomqtt
+from pydantic import ValidationError
 
 from .auth_mgr import YoLinkAuthMgr
-from .const import (
-    ATTR_DEVICE_SMART_REMOTER,
-    ATTR_DEVICE_WATER_DEPTH_SENSOR,
-    ATTR_DEVICE_WATER_METER_CONTROLLER,
-    ATTR_DEVICE_MULTI_WATER_METER_CONTROLLER,
-)
 from .device import YoLinkDevice
 from .message_listener import MessageListener
 from .model import BRDP
-from .message_resolver import (
-    smart_remoter_message_resolve,
-    water_depth_sensor_message_resolve,
-    water_meter_controller_message_resolve,
-    multi_water_meter_controller_message_resolve,
-)
+from .message_resolver import resolve_sub_message
 
 _LOGGER = logging.getLogger(__name__)
 
@@ -69,7 +55,7 @@ class YoLinkMqttClient:
                     port=self._broker_port,
                     username=self._auth_mgr.access_token(),
                     password="",
-                    keepalive=50,
+                    keepalive=60,
                 ) as client:
                     _LOGGER.info(
                         "[%s] connecting to yolink mqtt broker.", self._endpoint
@@ -127,6 +113,7 @@ class YoLinkMqttClient:
                     "getState",
                     "setState",
                     "DevEvent",
+                    "waterReport",  # Sprinkler
                 ]:
                     return
                 device = self._home_devices.get(device_id)
@@ -139,26 +126,15 @@ class YoLinkMqttClient:
                         return
                     # post current device state to paired device
                     paired_device_state = {"state": msg_data.data.get("state")}
-                    self.__resolve_message(msg_type, paired_device, paired_device_state)
-                self.__resolve_message(msg_type, device, msg_data.data)
+                    self.__resolve_message(paired_device, paired_device_state, msg_type)
+                self.__resolve_message(device, msg_data.data, msg_type)
             except ValidationError:
                 # ignore invalidate message
                 _LOGGER.debug("Message invalidate.")
 
     def __resolve_message(
-        self, event_type: str, device: YoLinkDevice, msg_data: dict[str, Any]
+        self, device: YoLinkDevice, msg_data: dict[str, Any], msg_type: str
     ) -> None:
         """Resolve device message."""
-        if device.device_type == ATTR_DEVICE_SMART_REMOTER:
-            msg_data = smart_remoter_message_resolve(event_type, msg_data)
-        if device.device_type == ATTR_DEVICE_WATER_DEPTH_SENSOR:
-            msg_data = water_depth_sensor_message_resolve(msg_data, device.device_attrs)
-        if device.device_type == ATTR_DEVICE_WATER_METER_CONTROLLER:
-            msg_data = water_meter_controller_message_resolve(
-                msg_data, device.device_model_name
-            )
-        if device.device_type == ATTR_DEVICE_MULTI_WATER_METER_CONTROLLER:
-            msg_data = multi_water_meter_controller_message_resolve(
-                msg_data, device.device_model_name
-            )
+        resolve_sub_message(device, msg_data, msg_type)
         self._message_listener.on_message(device, msg_data)
diff -pruN 0.5.4-1/yolink/thermostat_request_builder.py 0.5.8-1/yolink/thermostat_request_builder.py
--- 0.5.4-1/yolink/thermostat_request_builder.py	2025-05-29 08:43:20.000000000 +0000
+++ 0.5.8-1/yolink/thermostat_request_builder.py	2025-08-12 01:53:19.000000000 +0000
@@ -3,10 +3,7 @@ from __future__ import annotations
 
 from typing import Optional
 
-try:
-    from pydantic.v1 import BaseModel
-except ImportError:
-    from pydantic import BaseModel
+from pydantic import BaseModel
 
 from .client_request import ClientRequest
 
