From c3ed5991986f96fa8c27df0c57c8d7627c487e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E5=8F=A3=E6=B5=AA=E4=BA=BA?= Date: Sun, 27 Sep 2026 15:07:16 +0800 Subject: [PATCH] refactor: isolate OLED and RGB workers --- README.md | 2 +- SHA256SUMS | 4 +-- app/pigway_pi_control.py | 60 +++++++++++++++++++++++----------------- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 1743587..f172065 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ CHECK STORAGE ### 4. DisplayController、OLED 与 RGB -每个当前故障都是 DisplayController 中的一个 active request,包含稳定 ID、priority、severity、OLED 显示数据、首次发现时间、最后变化时间和元数据。监控只提交或清除 request,同一轮采样作为一个原子批次,全部更新后只仲裁一次。DisplayController 始终只选一个最高优先级 owner;同一 ID 更新原请求而不重复入队,恢复时删除请求,同优先级新 request 排队而不抢占,owner 恢复后按 priority、first_seen、ID 重新仲裁。OLED 在主页和全部当前告警页之间轮播,告警页右上角显示 `当前页/告警总数`,新 owner 会立即抢占一次 OLED。告警页显示时 RGB 同步表达该页对象;回到主页时 RGB 保持最高优先级 active owner,全部恢复后才 OFF。历史 power flags 只进入 diagnose 和一次性 Journal 记录。 +每个当前故障都是 DisplayController 中的一个 active request,包含稳定 ID、priority、severity、OLED 显示数据、首次发现时间、最后变化时间和元数据。监控只提交或清除 request,同一轮采样作为一个原子批次,全部更新后只仲裁一次。DisplayController 始终只选一个最高优先级 owner;同一 ID 更新原请求而不重复入队,恢复时删除请求,同优先级新 request 排队而不抢占,owner 恢复后按 priority、first_seen、ID 重新仲裁。OLED 与 RGB 分别由专属工作线程驱动,监控主循环只更新共享显示状态;OLED 在主页和全部当前告警页之间轮播,告警页右上角显示 `当前页/告警总数`,新 owner 会立即唤醒 OLED 线程。告警页显示时 RGB 同步表达该页对象;回到主页时 RGB 保持最高优先级 active owner,全部恢复后才 OFF。相邻 OLED 页面需要的 RGB mode 相同时,RGB 线程不写寄存器、不重置动画相位。历史 power flags 只进入 diagnose 和一次性 Journal 记录。 ### 5. RGB 正常状态与告警引擎 diff --git a/SHA256SUMS b/SHA256SUMS index 91de347..9950cbc 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,6 +1,6 @@ -96dfd20c3ad16ca62a46d66eb5ffb82b1f7f987e66c4dae5310609b1989b7dae README.md +95b1f56c564cecbcb32b402321b5bba0cb909bb979c07ff90abad9236a9a0e95 README.md 72c46162c33f9587c6ccb01ad53fbaaf4ecec8cd21014b909e87e8707e253e36 app/oled_font_5x7.bin -3084a16a3ec69a4938d86150bb29493264cb3c015ee944cbb9e2a4b33fb4b9cc app/pigway_pi_control.py +db6d52dec3e838717a46a4ec243f0cb54f72ede1c639cbadc35230186283d57c app/pigway_pi_control.py 034931590d83969f9f24222cc57800ab193588beea3d3dcedb98f1690000370d app/pigway_pi_control_api.py ee0f682e8016f75f906d974f00c3e3340edddf30447d441ddfd61a7a34c9e83c config/pigway-pi-control.conf f9fbb4defa45b43abe0ce66973b1f4d553369b3a43c01d04f206c573f7dc96ca install.sh diff --git a/app/pigway_pi_control.py b/app/pigway_pi_control.py index 17d3466..f5082b3 100755 --- a/app/pigway_pi_control.py +++ b/app/pigway_pi_control.py @@ -582,10 +582,12 @@ class DisplayController: self.page_started=now self.batch_depth=0 self.stop_event=threading.Event() + self.oled_wake=threading.Event() + self.oled_thread=threading.Thread(target=self._oled_worker,name="oled",daemon=True) self.rgb_thread=threading.Thread(target=self._rgb_worker,name="rgb",daemon=True) def start(self): - oled_init(); self.refresh(); rgb_off(); self.rgb_thread.start() + oled_init(); rgb_off(); self.oled_thread.start(); self.rgb_thread.start() def update_home(self,cpu,temp,mem,disk): with self.lock: @@ -623,7 +625,7 @@ class DisplayController: if self.batch_depth==0: self._arbitrate_locked() if self.current_oled_page_id not in self.requests: - self._show_page_locked(self.current_owner_id) + self._select_page_locked(self.current_owner_id) else: self._sync_rgb_locked() def begin_update(self): @@ -636,7 +638,7 @@ class DisplayController: if self.batch_depth==0: self._arbitrate_locked() if self.current_oled_page_id not in self.requests: - self._show_page_locked(self.current_owner_id) + self._select_page_locked(self.current_owner_id) else: self._sync_rgb_locked() def _arbitrate_locked(self): @@ -654,23 +656,18 @@ class DisplayController: self.current_rgb_mode=request["rgb_mode"] self.owner_since=time.monotonic() log("INFO","DISPLAY_OWNER",old=old,new=request["id"],priority=request["priority"]) - self._show_page_locked(request["id"]) + self._select_page_locked(request["id"]) def _ordered_alerts_locked(self): return sorted((r for r in self.requests.values() if r["id"]!="NORMAL_HOME"), key=lambda r:(-r["priority"],r["first_seen"],r["id"])) - def _show_page_locked(self,page_id): - alerts=self._ordered_alerts_locked() + def _select_page_locked(self,page_id,wake=True): request=self.requests.get(page_id,self.requests["NORMAL_HOME"]) self.current_oled_page_id=request["id"] self.page_started=time.monotonic() self._sync_rgb_locked() - if request["id"]=="NORMAL_HOME": - render_home(*request["metadata"]["home"]) - else: - alert_ids=[a["id"] for a in alerts] - render_alert(request,alert_ids.index(request["id"])+1,len(alerts)) + if wake: self.oled_wake.set() def _sync_rgb_locked(self): # Alert pages show that alert's object color. HOME keeps the highest- @@ -680,24 +677,23 @@ class DisplayController: rgb_request=self.requests.get(self.current_owner_id,self.requests["NORMAL_HOME"]) if rgb_request is None: rgb_request=self.requests["NORMAL_HOME"] - self.current_rgb_mode=rgb_request["rgb_mode"] + wanted_mode=rgb_request["rgb_mode"] + if wanted_mode!=self.current_rgb_mode: + self.current_rgb_mode=wanted_mode def refresh(self): with self.lock: alerts=self._ordered_alerts_locked() pages=["NORMAL_HOME"]+[a["id"] for a in alerts] if self.current_oled_page_id not in pages: - self._show_page_locked(self.current_owner_id) - return - if len(pages)>1 and time.monotonic()-self.page_started>=PAGE_INTERVAL: + self._select_page_locked(self.current_owner_id,wake=False) + elif len(pages)>1 and time.monotonic()-self.page_started>=PAGE_INTERVAL: pos=pages.index(self.current_oled_page_id) - self._show_page_locked(pages[(pos+1)%len(pages)]) - return - request=self.requests[self.current_oled_page_id] - if request["id"]=="NORMAL_HOME": render_home(*request["metadata"]["home"]) - else: - alert_ids=[a["id"] for a in alerts] - render_alert(request,alert_ids.index(request["id"])+1,len(alerts)) + self._select_page_locked(pages[(pos+1)%len(pages)],wake=False) + request=dict(self.requests[self.current_oled_page_id]) + alert_ids=[a["id"] for a in alerts] + if request["id"]=="NORMAL_HOME": render_home(*request["metadata"]["home"]) + else: render_alert(request,alert_ids.index(request["id"])+1,len(alerts)) def owner_snapshot(self): with self.lock: return dict(self.requests[self.current_owner_id]) @@ -713,6 +709,19 @@ class DisplayController: for a in alerts], } + def _oled_worker(self): + previous_error=None + while not self.stop_event.is_set(): + self.oled_wake.clear() + try: + self.refresh() + if previous_error is not None: log("INFO","OLED_RECOVERED") + previous_error=None + except Exception as e: + if repr(e)!=previous_error: log("ERROR","OLED_ERROR",error=repr(e)) + previous_error=repr(e) + self.oled_wake.wait(OLED_REFRESH) + def _rgb_worker(self): previous_error=None; applied_mode="OFF"; applied_flash=False flash_on=None; animation_started=time.monotonic() @@ -757,6 +766,8 @@ class DisplayController: def shutdown(self): self.stop_event.set() + self.oled_wake.set() + if self.oled_thread.is_alive(): self.oled_thread.join() if self.rgb_thread.is_alive(): self.rgb_thread.join() for name,action in (("rgb",rgb_off),("oled_clear",lambda:oled_show([("","")]*4)), ("oled_off",lambda:cmd(0xAE))): @@ -811,7 +822,7 @@ args=parser.parse_args() if args.diagnose: raise SystemExit(diagnose()) watch_state=[] -last_sensor=last_render=last_status=0.0 +last_sensor=last_status=0.0 last_net=last_watch=last_health=0.0 health={}; fs_readonly=False agent={"last_sensor_ok":0.0,"last_network_ok":0.0,"last_watch_ok":0.0,"last_health_ok":0.0,"loop_errors":0,"i2c_errors":0} @@ -874,9 +885,6 @@ try: display.begin_update() try: update_active_alerts(c,t,m,d,sample_elapsed) finally: display.end_update() - if now-last_render >= OLED_REFRESH: - display.refresh() - last_render=now if now-last_status >= 1.0: write_status(c,t,m,d); last_status=now