Skip to content

Commit 163b9b9

Browse files
committed
refactor: replace @auto_refresh with simpler @dirties_state decorator
- Remove complex automatic refresh system in favor of explicit state tracking. - Add dirty flags to OmniLogic and update refresh() with flexible if_dirty/if_older_than/force parameters for better user control.
1 parent 69b9e51 commit 163b9b9

File tree

4 files changed

+77
-144
lines changed

4 files changed

+77
-144
lines changed

pyomnilogic_local/_base.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -97,25 +97,3 @@ def update_telemetry(self, telemetry: Telemetry) -> None:
9797
self.telemetry = cast(TelemetryT, specific_telemetry)
9898
else:
9999
self.telemetry = cast(TelemetryT, None)
100-
# else:
101-
# raise NotImplementedError("This equipment does not have telemetry data.")
102-
103-
# def _update_equipment(self, telemetry: Telemetry) -> None:
104-
# pass
105-
106-
# def _update_equipment(self, telemetry: Telemetry) -> None:
107-
# """Update any child equipment based on the latest MSPConfig and Telemetry data."""
108-
# for _, equipment_mspconfig in self.mspconfig:
109-
# system_id = equipment_mspconfig.system_id
110-
# if system_id is None:
111-
# _LOGGER.debug("Skipping equipment update: system_id is None: %s", equipment_mspconfig)
112-
# continue
113-
# if system_id in self.child_equipment:
114-
# # Update existing child equipment
115-
# child_equipment = self.child_equipment[system_id]
116-
# if child_equipment is not None:
117-
# child_equipment.update_config(equipment_mspconfig)
118-
# if hasattr(self, "telemetry"):
119-
# child_equipment.update_telemetry(telemetry)
120-
# else:
121-
# equipment = create_equipment(self, equipment_mspconfig, telemetry)

pyomnilogic_local/colorlogiclight.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import TYPE_CHECKING
33

44
from pyomnilogic_local._base import OmniEquipment
5-
from pyomnilogic_local.decorators import auto_refresh
5+
from pyomnilogic_local.decorators import dirties_state
66
from pyomnilogic_local.models.mspconfig import MSPColorLogicLight
77
from pyomnilogic_local.models.telemetry import Telemetry, TelemetryColorLogicLight
88
from pyomnilogic_local.omnitypes import (
@@ -72,21 +72,21 @@ def special_effect(self) -> int:
7272
"""Returns the current special effect."""
7373
return self.telemetry.special_effect
7474

75-
@auto_refresh()
75+
@dirties_state()
7676
async def turn_on(self) -> None:
7777
"""Turns the light on."""
7878
if self.bow_id is None or self.system_id is None:
7979
raise OmniEquipmentNotInitializedError("Cannot turn on light: bow_id or system_id is None")
8080
await self._api.async_set_equipment(self.bow_id, self.system_id, True)
8181

82-
@auto_refresh()
82+
@dirties_state()
8383
async def turn_off(self) -> None:
8484
"""Turns the light off."""
8585
if self.bow_id is None or self.system_id is None:
8686
raise OmniEquipmentNotInitializedError("Cannot turn off light: bow_id or system_id is None")
8787
await self._api.async_set_equipment(self.bow_id, self.system_id, False)
8888

89-
@auto_refresh()
89+
@dirties_state()
9090
async def set_show(
9191
self, show: LightShows | None = None, speed: ColorLogicSpeed | None = None, brightness: ColorLogicBrightness | None = None
9292
) -> None:

pyomnilogic_local/decorators.py

Lines changed: 23 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
"""Decorators for automatic state management in pyomnilogic_local."""
1+
"""Decorators for equipment control methods."""
22

3-
import asyncio
43
import functools
54
import logging
6-
import time
75
from collections.abc import Callable
86
from typing import Any, TypeVar, cast
97

@@ -12,82 +10,37 @@
1210
F = TypeVar("F", bound=Callable[..., Any])
1311

1412

15-
def auto_refresh(
16-
update_mspconfig: bool = False,
17-
update_telemetry: bool = True,
18-
delay: float = 1.25,
19-
) -> Callable[[F], F]:
20-
"""Decorator to automatically refresh OmniLogic state after method execution.
13+
def dirties_state(mspconfig: bool = False, telemetry: bool = True) -> Callable[[F], F]:
14+
"""Mark state as dirty after equipment control methods.
2115
22-
This decorator will:
23-
1. Execute the decorated method
24-
2. Wait for the specified delay (to allow controller to update)
25-
3. Refresh telemetry/mspconfig if they're older than the post-delay time
26-
27-
The decorator is lock-safe: if multiple decorated methods are called concurrently,
28-
only one refresh will occur thanks to the update_if_older_than mechanism.
16+
This decorator marks the OmniLogic state (telemetry and/or mspconfig) as dirty
17+
after a control method executes, indicating that the cached state is likely
18+
out of sync with reality. Users can then call refresh() to update the state.
2919
3020
Args:
31-
update_mspconfig: Whether to refresh MSPConfig after method execution
32-
update_telemetry: Whether to refresh Telemetry after method execution
33-
delay: Time in seconds to wait after method completes before refreshing
34-
35-
Usage:
36-
@auto_refresh() # Default: telemetry only, 0.25s delay
37-
async def turn_on(self, auto_refresh: bool | None = None):
38-
...
39-
40-
@auto_refresh(update_mspconfig=True, delay=0.5)
41-
async def configure(self, auto_refresh: bool | None = None):
42-
...
21+
mspconfig: Whether to mark mspconfig as dirty (default: False)
22+
telemetry: Whether to mark telemetry as dirty (default: True)
4323
44-
The decorated method can accept an optional `auto_refresh` parameter:
45-
- If None (default): Uses the OmniLogic instance's auto_refresh_enabled setting
46-
- If True: Forces auto-refresh regardless of instance setting
47-
- If False: Disables auto-refresh for this call
24+
Example:
25+
@dirties_state(telemetry=True)
26+
async def turn_on(self) -> None:
27+
await self._api.async_set_equipment(...)
4828
"""
4929

5030
def decorator(func: F) -> F:
5131
@functools.wraps(func)
52-
async def wrapper(*args: Any, **kwargs: Any) -> Any:
53-
# Extract the 'auto_refresh' parameter if provided
54-
auto_refresh_param = kwargs.pop("auto_refresh", None)
55-
56-
# First arg should be 'self' (equipment instance)
57-
if not args:
58-
raise RuntimeError("@auto_refresh decorator requires a method with 'self' parameter")
59-
60-
self_obj = args[0]
61-
62-
# Get the OmniLogic instance
63-
# Equipment classes should have _omni attribute pointing to parent OmniLogic
64-
if hasattr(self_obj, "_omni") and self_obj._omni is not None: # pylint: disable=protected-access
65-
omni = self_obj._omni # pylint: disable=protected-access
66-
elif hasattr(self_obj, "auto_refresh_enabled"):
67-
# This IS the OmniLogic instance
68-
omni = self_obj
32+
async def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
33+
# Execute the original function
34+
result = await func(self, *args, **kwargs)
35+
36+
# Mark state as dirty
37+
if hasattr(self, "_omni"):
38+
if telemetry:
39+
self._omni._telemetry_dirty = True # pylint: disable=protected-access
40+
if mspconfig:
41+
self._omni._mspconfig_dirty = True # pylint: disable=protected-access
6942
else:
70-
raise RuntimeError("@auto_refresh decorator requires equipment to have '_omni' attribute or be used on OmniLogic methods")
71-
72-
# Determine if we should auto-refresh
73-
should_refresh = auto_refresh_param if auto_refresh_param is not None else omni.auto_refresh_enabled
74-
75-
# Execute the original method
76-
result = await func(*args, **kwargs)
77-
78-
# Perform auto-refresh if enabled
79-
if should_refresh:
80-
# Wait for the controller to process the change
81-
await asyncio.sleep(delay)
82-
83-
# Calculate the target time (after delay)
84-
target_time = time.time()
85-
86-
# Update only if data is older than target time
87-
await omni.update_if_older_than(
88-
telemetry_min_time=target_time if update_telemetry else None,
89-
mspconfig_min_time=target_time if update_mspconfig else None,
90-
)
43+
_LOGGER.warning("%s does not have _omni reference, cannot mark state as dirty", self.__class__.__name__)
9144

9245
return result
9346

pyomnilogic_local/omnilogic.py

Lines changed: 50 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111

1212

1313
class OmniLogic:
14-
auto_refresh_enabled: bool = True
15-
1614
mspconfig: MSPConfig
1715
telemetry: Telemetry
1816

@@ -21,6 +19,8 @@ class OmniLogic:
2119

2220
_mspconfig_last_updated: float = 0.0
2321
_telemetry_last_updated: float = 0.0
22+
_mspconfig_dirty: bool = True
23+
_telemetry_dirty: bool = True
2424
_refresh_lock: asyncio.Lock
2525

2626
def __init__(self, host: str, port: int = 10444) -> None:
@@ -30,58 +30,60 @@ def __init__(self, host: str, port: int = 10444) -> None:
3030
self._api = OmniLogicAPI(host, port)
3131
self._refresh_lock = asyncio.Lock()
3232

33-
async def refresh(self, update_mspconfig: bool = True, update_telemetry: bool = True) -> None:
34-
"""Refresh the data from the OmniLogic controller.
35-
36-
Args:
37-
update_mspconfig: Whether to fetch and update MSPConfig data
38-
update_telemetry: Whether to fetch and update Telemetry data
39-
"""
40-
if update_mspconfig:
41-
self.mspconfig = await self._api.async_get_mspconfig()
42-
self._mspconfig_last_updated = time.time()
43-
if update_telemetry:
44-
self.telemetry = await self._api.async_get_telemetry()
45-
self._telemetry_last_updated = time.time()
46-
47-
self._update_equipment()
48-
49-
# async def refresh_mspconfig(self) -> None:
50-
# """Refresh only the MSPConfig data from the OmniLogic controller."""
51-
# self.mspconfig = await self._api.async_get_mspconfig()
52-
# self._mspconfig_last_updated = time.time()
53-
# self._update_equipment()
54-
55-
# async def refresh_telemetry(self) -> None:
56-
# """Refresh only the Telemetry data from the OmniLogic controller."""
57-
# self.telemetry = await self._api.async_get_telemetry()
58-
# self._telemetry_last_updated = time.time()
59-
# self._update_equipment()
60-
61-
async def update_if_older_than(
33+
async def refresh(
6234
self,
63-
telemetry_min_time: float | None = None,
64-
mspconfig_min_time: float | None = None,
35+
*,
36+
mspconfig: bool = True,
37+
telemetry: bool = True,
38+
if_dirty: bool = True,
39+
if_older_than: float = 10.0,
40+
force: bool = False,
6541
) -> None:
66-
"""Update telemetry/mspconfig only if older than specified timestamp.
67-
68-
This method uses a lock to ensure only one refresh happens at a time.
69-
If another thread/task already updated the data to be newer than required,
70-
this method will skip the update.
42+
"""Refresh the data from the OmniLogic controller.
7143
7244
Args:
73-
telemetry_min_time: Update telemetry if last updated before this timestamp
74-
mspconfig_min_time: Update mspconfig if last updated before this timestamp
45+
mspconfig: Whether to refresh MSPConfig data (if conditions are met)
46+
telemetry: Whether to refresh Telemetry data (if conditions are met)
47+
if_dirty: Only refresh if the data has been marked dirty
48+
if_older_than: Only refresh if data is older than this many seconds
49+
force: Force refresh regardless of dirty flag or age
7550
"""
7651
async with self._refresh_lock:
77-
needs_telemetry = telemetry_min_time and self._telemetry_last_updated < telemetry_min_time
78-
needs_mspconfig = mspconfig_min_time and self._mspconfig_last_updated < mspconfig_min_time
79-
80-
if needs_telemetry or needs_mspconfig:
81-
await self.refresh(
82-
update_mspconfig=bool(needs_mspconfig),
83-
update_telemetry=bool(needs_telemetry),
84-
)
52+
current_time = time.time()
53+
54+
# Determine if mspconfig needs updating
55+
update_mspconfig = False
56+
if mspconfig:
57+
if force:
58+
update_mspconfig = True
59+
elif if_dirty and self._mspconfig_dirty:
60+
update_mspconfig = True
61+
elif (current_time - self._mspconfig_last_updated) > if_older_than:
62+
update_mspconfig = True
63+
64+
# Determine if telemetry needs updating
65+
update_telemetry = False
66+
if telemetry:
67+
if force:
68+
update_telemetry = True
69+
elif if_dirty and self._telemetry_dirty:
70+
update_telemetry = True
71+
elif (current_time - self._telemetry_last_updated) > if_older_than:
72+
update_telemetry = True
73+
74+
# Perform the updates
75+
if update_mspconfig:
76+
self.mspconfig = await self._api.async_get_mspconfig()
77+
self._mspconfig_last_updated = time.time()
78+
self._mspconfig_dirty = False
79+
80+
if update_telemetry:
81+
self.telemetry = await self._api.async_get_telemetry()
82+
self._telemetry_last_updated = time.time()
83+
self._telemetry_dirty = False
84+
85+
if update_mspconfig or update_telemetry:
86+
self._update_equipment()
8587

8688
def _update_equipment(self) -> None:
8789
"""Update equipment objects based on the latest MSPConfig and Telemetry data."""

0 commit comments

Comments
 (0)