Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions examples/blinds/blinds_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
23 changes: 23 additions & 0 deletions sinricpro/capabilities/open_close_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
7 changes: 6 additions & 1 deletion sinricpro/devices/sinric_pro_blinds.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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")
Expand Down
Loading