From 85eeb14898eeee55437d2ec0204dae2025761b76 Mon Sep 17 00:00:00 2001 From: Aruna Tennakoon Date: Thu, 21 May 2026 09:28:45 +0700 Subject: [PATCH] fix: https://github.com/sinricpro/python-sdk/issues/85 --- examples/blinds/blinds_example.py | 22 ++++++++++++++++++ pyproject.toml | 4 ++-- .../capabilities/open_close_controller.py | 23 +++++++++++++++++++ sinricpro/devices/sinric_pro_blinds.py | 7 +++++- 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/examples/blinds/blinds_example.py b/examples/blinds/blinds_example.py index 5fc7b1d..2968374 100644 --- a/examples/blinds/blinds_example.py +++ b/examples/blinds/blinds_example.py @@ -40,6 +40,27 @@ async def on_open_close(position: int) -> bool: print(f"[Hardware] Blinds moved to {position}%") return True +async def on_adjust_open_close(position_delta: int) -> bool: + """ + Handle relative open/close position change requests (e.g. "Alexa, close the blinds"). + + Args: + position_delta: Signed delta to apply to the current position (positive = open more, + negative = close more). Alexa sends e.g. -10 for a small close step. + + Returns: + True if successful, False otherwise + """ + global current_position + new_position = max(0, min(100, current_position + position_delta)) + print(f"\n[Callback] Adjusting blinds by {position_delta}% -> {new_position}%") + current_position = new_position + + # TODO: Control your motor to move blinds to new_position + + print(f"[Hardware] Blinds moved to {new_position}%") + return True + async def on_power_state(state: bool) -> bool: """ Handle power state change requests. @@ -112,6 +133,7 @@ async def main() -> None: # Register callbacks blinds.on_power_state(on_power_state) blinds.on_open_close(on_open_close) + blinds.on_adjust_open_close(on_adjust_open_close) blinds.on_setting(on_setting) # Add device to SinricPro diff --git a/pyproject.toml b/pyproject.toml index 432c00a..6b531ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,9 +4,9 @@ build-backend = "setuptools.build_meta" [project] name = "sinricpro" -version = "5.2.1" +version = "5.3.1" description = "Official SinricPro SDK for Python - Control IoT devices with Alexa and Google Home" -authors = [{name = "SinricPro", email = "support@sinric.pro"}] +authors = [{name = "SinricPro", email = "support@sinric.com"}] readme = "README.md" license = {text = "CC-BY-SA-4.0"} requires-python = ">=3.10" diff --git a/sinricpro/capabilities/open_close_controller.py b/sinricpro/capabilities/open_close_controller.py index e6734d5..0407536 100644 --- a/sinricpro/capabilities/open_close_controller.py +++ b/sinricpro/capabilities/open_close_controller.py @@ -9,18 +9,29 @@ from sinricpro.core.sinric_pro_device import SinricProDevice OpenCloseCallback = Callable[[int], Awaitable[bool]] # 0-100 (0=closed, 100=open) +AdjustOpenCloseCallback = Callable[[int], Awaitable[bool]] # signed delta to apply to position class OpenCloseController: """Mixin providing open/close control capability.""" def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self._open_close_callback: OpenCloseCallback | None = None + self._adjust_open_close_callback: AdjustOpenCloseCallback | None = None self._open_close_limiter = EventLimiter(EVENT_LIMIT_STATE) def on_open_close(self, callback: OpenCloseCallback) -> None: """Register callback for open/close position changes (0=closed, 100=open).""" self._open_close_callback = callback + def on_adjust_open_close(self, callback: AdjustOpenCloseCallback) -> None: + """Register callback for relative open/close position changes. + + Args: + callback: Async function that receives a signed position delta and returns True on success. + Signature: async def callback(position_delta: int) -> bool + """ + self._adjust_open_close_callback = callback + async def handle_open_close_request(self, position: int, device: "SinricProDevice") -> tuple[bool, dict[str, Any]]: """Handle setRangeValue request (used for open/close).""" if not self._open_close_callback: @@ -36,6 +47,18 @@ async def handle_open_close_request(self, position: int, device: "SinricProDevic SinricProLogger.error(f"Error in open/close callback: {e}") return False, {} + async def handle_adjust_open_close_request(self, position_delta: int, device: "SinricProDevice") -> tuple[bool, dict[str, Any]]: + """Handle adjustRangeValue request (used for relative open/close adjustments).""" + if not self._adjust_open_close_callback: + SinricProLogger.error(f"No adjust open/close callback registered for {device.get_device_id()}") + return False, {} + try: + success = await self._adjust_open_close_callback(position_delta) + return (True, {"rangeValue": position_delta}) if success else (False, {}) + except Exception as e: + SinricProLogger.error(f"Error in adjust open/close callback: {e}") + return False, {} + async def send_open_close_event(self, position: int, cause: str = "PHYSICAL_INTERACTION") -> bool: """Send open/close position event (0=closed, 100=open).""" if not 0 <= position <= 100: diff --git a/sinricpro/devices/sinric_pro_blinds.py b/sinricpro/devices/sinric_pro_blinds.py index af9e3db..fedc97b 100644 --- a/sinricpro/devices/sinric_pro_blinds.py +++ b/sinricpro/devices/sinric_pro_blinds.py @@ -3,7 +3,7 @@ from sinricpro.capabilities.power_state_controller import PowerStateController from sinricpro.capabilities.push_notification import PushNotification from sinricpro.capabilities.setting_controller import SettingController -from sinricpro.core.actions import ACTION_SET_POWER_STATE, ACTION_SET_RANGE_VALUE, ACTION_SET_SETTING +from sinricpro.core.actions import ACTION_ADJUST_RANGE_VALUE, ACTION_SET_POWER_STATE, ACTION_SET_RANGE_VALUE, ACTION_SET_SETTING from sinricpro.core.sinric_pro_device import SinricProDevice from sinricpro.core.types import SinricProRequest @@ -23,6 +23,11 @@ async def handle_request(self, request: SinricProRequest) -> bool: success, response_value = await self.handle_open_close_request(position, self) request.response_value = response_value return success + elif request.action == ACTION_ADJUST_RANGE_VALUE: + position_delta = request.request_value.get("rangeValueDelta", 0) + success, response_value = await self.handle_adjust_open_close_request(position_delta, self) + request.response_value = response_value + return success elif request.action == ACTION_SET_SETTING: setting_id = request.request_value.get("id", "") value = request.request_value.get("value")