feat(config,detector): TelegramCfg polling fields + Detector.step optional frame
TelegramCfg gains allowed_chat_ids (default: [chat_id]), poll_timeout_s=30, auto_poll_interval_s=180. _from_dict reads from TOML; absent key defaults to primary chat_id so existing configs need no changes. Detector.step(ts, frame=None): when frame is provided the capture() call is skipped — async loop pre-captures once, shares frame between canary+detection. DetectionResult.dot_pos_abs carries absolute (x,y) for price overlay. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -78,6 +78,9 @@ class DiscordCfg:
|
|||||||
class TelegramCfg:
|
class TelegramCfg:
|
||||||
bot_token: str
|
bot_token: str
|
||||||
chat_id: str
|
chat_id: str
|
||||||
|
allowed_chat_ids: tuple[str, ...] = ()
|
||||||
|
poll_timeout_s: int = 30
|
||||||
|
auto_poll_interval_s: int = 180
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
if not self.bot_token or not self.chat_id:
|
if not self.bot_token or not self.chat_id:
|
||||||
@@ -156,9 +159,14 @@ class Config:
|
|||||||
drift_threshold=int(data["canary"].get("drift_threshold", 8)),
|
drift_threshold=int(data["canary"].get("drift_threshold", 8)),
|
||||||
)
|
)
|
||||||
discord = DiscordCfg(webhook_url=data["discord"]["webhook_url"])
|
discord = DiscordCfg(webhook_url=data["discord"]["webhook_url"])
|
||||||
|
tg = data["telegram"]
|
||||||
|
_allowed = [str(c) for c in tg.get("allowed_chat_ids", [])] or [str(tg["chat_id"])]
|
||||||
telegram = TelegramCfg(
|
telegram = TelegramCfg(
|
||||||
bot_token=data["telegram"]["bot_token"],
|
bot_token=tg["bot_token"],
|
||||||
chat_id=str(data["telegram"]["chat_id"]),
|
chat_id=str(tg["chat_id"]),
|
||||||
|
allowed_chat_ids=tuple(_allowed),
|
||||||
|
poll_timeout_s=int(tg.get("poll_timeout_s", 30)),
|
||||||
|
auto_poll_interval_s=int(tg.get("auto_poll_interval_s", 180)),
|
||||||
)
|
)
|
||||||
opts = data.get("options", {})
|
opts = data.get("options", {})
|
||||||
region = None
|
region = None
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class DetectionResult:
|
|||||||
match: ColorMatch | None # None if no dot
|
match: ColorMatch | None # None if no dot
|
||||||
accepted: bool # post-debounce; True only when match repeats debounce_depth times
|
accepted: bool # post-debounce; True only when match repeats debounce_depth times
|
||||||
color: str | None # accepted color name (UNKNOWN excluded)
|
color: str | None # accepted color name (UNKNOWN excluded)
|
||||||
|
dot_pos_abs: tuple[int, int] | None = None # absolute (x, y) in frame; set when dot_found
|
||||||
|
|
||||||
|
|
||||||
class Detector:
|
class Detector:
|
||||||
@@ -60,7 +61,13 @@ class Detector:
|
|||||||
self._debounce: deque[str | None] = deque(maxlen=cfg.debounce_depth)
|
self._debounce: deque[str | None] = deque(maxlen=cfg.debounce_depth)
|
||||||
self._rolling: deque[DetectionResult] = deque(maxlen=20)
|
self._rolling: deque[DetectionResult] = deque(maxlen=20)
|
||||||
|
|
||||||
def step(self, ts: float) -> DetectionResult:
|
def step(self, ts: float, frame=None) -> DetectionResult:
|
||||||
|
"""Run one detection tick.
|
||||||
|
|
||||||
|
frame: pre-captured BGR ndarray (from asyncio.to_thread capture). When
|
||||||
|
None (default), calls self._capture() — preserving the sync-loop behaviour.
|
||||||
|
"""
|
||||||
|
if frame is None:
|
||||||
frame = self._capture()
|
frame = self._capture()
|
||||||
|
|
||||||
if frame is None:
|
if frame is None:
|
||||||
@@ -117,6 +124,7 @@ class Detector:
|
|||||||
match=match,
|
match=match,
|
||||||
accepted=accepted,
|
accepted=accepted,
|
||||||
color=color,
|
color=color,
|
||||||
|
dot_pos_abs=(self._cfg.dot_roi.x + x, self._cfg.dot_roi.y + y),
|
||||||
)
|
)
|
||||||
self._rolling.append(r)
|
self._rolling.append(r)
|
||||||
return r
|
return r
|
||||||
|
|||||||
Reference in New Issue
Block a user