diff --git a/README.md b/README.md index d600781..c8f68ea 100644 --- a/README.md +++ b/README.md @@ -69,11 +69,11 @@ CHECK STORAGE ### 4. DisplayController、OLED 与 RGB -每个当前故障都是 DisplayController 中的一个 active request,包含稳定 ID、priority、severity、OLED 显示数据、首次发现时间、最后变化时间和元数据。监控只提交或清除 request,同一轮采样作为一个原子批次,全部更新后只仲裁一次。DisplayController 始终只选一个 owner;同优先级新 request 排队而不抢占,owner 恢复后按 priority、first_seen、ID 重新仲裁。OLED 在主页和全部当前告警页之间轮播,告警页右上角显示 `当前页/告警总数`,新 owner 会立即抢占一次 OLED。OLED 页面游标不参与 RGB 决策,因此切回主页或其他告警页不会关闭、重启或改变灯效。历史 power flags 只进入 diagnose 和一次性 Journal 记录。 +每个当前故障都是 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 记录。 ### 5. RGB 告警引擎 -RGB 模式的唯一来源是 current Display Owner 的 severity。Warning 使用 MCU 内置黄色慢呼吸,Critical 使用 MCU 内置红色快呼吸,Emergency 为纯红快速频闪;`NORMAL_HOME` 为 owner 时 OFF。仅在 RGB mode 实际变化时按 `speed → color → effect` 写入。呼吸动画完全由 MCU 生成,不再使用软件 PWM 或逐帧 R/G/B 写入。owner 变化但 RGB mode 相同时不关闭、不重启灯效。动画不写日志,owner 变化记录 `DISPLAY_OWNER`,实际模式变化才记录 `RGB_MODE`。 +RGB 模式由当前 OLED 告警页决定;OLED 位于主页时则使用最高优先级 active owner。颜色表示故障对象:CPU/供电/降频为黄色,温度为红色,内存为紫色,磁盘为白色,网络/Wi-Fi 为蓝色,服务/进程为青色。速度表示严重等级:Warning 慢呼吸、Critical 快呼吸、Emergency 纯红快速频闪;没有 active alert 时 OFF。模式变化时按 `speed → color → effect` 写入,呼吸动画完全由 MCU 生成,不使用软件 PWM 或逐帧 R/G/B 写入。相邻页面 RGB mode 相同时不关闭、不重启灯效。动画不写循环日志,owner 变化记录 `DISPLAY_OWNER`,实际模式变化才记录 `RGB_MODE`。 ### 6. 结构化事件日志 diff --git a/SHA256SUMS b/SHA256SUMS index 18c8a71..9ac4ab8 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,7 +1,7 @@ -e197d388988c8fa69adc1995de66d0dafc63d8ebf24326ad2e59743c201315d1 README.md +1dd807bcc8e6711fed829f42e84f8baf4d644e7248a5a39bd17f65d14090e35e README.md 72c46162c33f9587c6ccb01ad53fbaaf4ecec8cd21014b909e87e8707e253e36 app/oled_font_5x7.bin -e530efb5a08b0413836bb91dd08651cfe724b1cca616794cabd5b69ae7ec83cd app/pigway_pi_control.py -5c39712ddca8a9846e7cde81b91d3af2399a326c1fe437c66050d140b3f37fb6 config/pigway-pi-control.conf +e04cd1b7940ce5fa0539586dbaaac654a3234aa6e0306d77d1e4b2b1c918ab9c app/pigway_pi_control.py +7155b4585c8caf60ef8d9a0c0676229b742d86bd938fbd9303ca9bfbb8964be6 config/pigway-pi-control.conf 4c2816da08378f2927c746b70ecfa7d5312e1730c08727cd89e8c2801e707054 install.sh 81aea0a89b298b2512fcb9bf6418618ba7a66474d425390cf95a0128c4093474 systemd/pigway-pi-control.service a3d74973cafbabaf58a660a8c83d28347d1c06d9fd2dab3ecfce5b7607f6fee2 uninstall.sh diff --git a/app/pigway_pi_control.py b/app/pigway_pi_control.py index 922ed6f..1f483f3 100755 --- a/app/pigway_pi_control.py +++ b/app/pigway_pi_control.py @@ -123,18 +123,15 @@ def rgb_off(): def rgb_mode(mode): global _last_rgb - settings={ - "WARNING_BREATHE":(RGB_WARNING_SPEED,RGB_WARNING_COLOR,0x01), - "CRITICAL_BREATHE":(RGB_CRITICAL_SPEED,RGB_CRITICAL_COLOR,0x01), - } - state=settings.get(mode) - if state is None: raise ValueError(f"unsupported RGB mode: {mode}") + match=re.fullmatch(r"(RED|GREEN|BLUE|YELLOW|PURPLE|CYAN|WHITE)_(SLOW|FAST)_BREATHE",mode) + if match is None: raise ValueError(f"unsupported RGB mode: {mode}") if _last_rgb==(mode,): return - speed,color,effect=state + color=RGB_COLOR_VALUES[match.group(1)] + speed=RGB_WARNING_SPEED if match.group(2)=="SLOW" else RGB_CRITICAL_SPEED # Hardware-calibrated MCU order. The final effect write commits the mode. mcu(0x05,speed); time.sleep(RGB_WRITE_DELAY_MS/1000.0) mcu(0x06,color); time.sleep(RGB_WRITE_DELAY_MS/1000.0) - mcu(0x04,effect) + mcu(0x04,0x01) _last_rgb=(mode,) def set_pi_led(name,off=True): @@ -267,9 +264,17 @@ DARK_ACT=cf("dark_mode","pi_act_led",True,lambda x: str(x).strip().lower() in (" EMERGENCY_FLASH_MS=cf("led","emergency_flash_ms",300,int) RGB_WRITE_DELAY_MS=max(1,cf("led","write_delay_ms",10,int)) RGB_WARNING_SPEED=max(1,min(3,cf("led","warning_speed",1,int))) -RGB_WARNING_COLOR=max(0,min(6,cf("led","warning_color",3,int))) RGB_CRITICAL_SPEED=max(1,min(3,cf("led","critical_speed",3,int))) -RGB_CRITICAL_COLOR=max(0,min(6,cf("led","critical_color",0,int))) +RGB_COLOR_VALUES={"RED":0,"GREEN":1,"BLUE":2,"YELLOW":3,"PURPLE":4,"CYAN":5,"WHITE":6} +RGB_OBJECT_COLORS={ + "ROOT_READONLY":"RED", + "TEMP_CRITICAL":"RED","TEMP_HIGH":"RED","SOFT_TEMP_LIMIT":"RED", + "POWER_UNDERVOLT":"YELLOW","THROTTLED":"YELLOW","FREQ_CAPPED":"YELLOW","CPU_HIGH":"YELLOW", + "MEMORY_HIGH":"PURPLE", + "DISK_CRITICAL":"WHITE","DISK_HIGH":"WHITE", + "NETWORK_DOWN":"BLUE","INTERNET_DOWN":"BLUE","WIFI_WEAK":"BLUE", + "SERVICE_DOWN":"CYAN","PROCESS_DOWN":"CYAN", +} def check_watch(w): typ,label,target=w @@ -413,11 +418,11 @@ def update_active_alerts(cpu,temp,mem,disk,sample_elapsed=0.0): if fs_readonly: display.request("ROOT_READONLY",3,"FILESYSTEM RO","ROOT READ ONLY","CHECK STORAGE","EMERGENCY") else: display.clear("ROOT_READONLY") -def rgb_mode_for(a): - if not a: return "OFF" - if a["sev"]>=3: return "EMERGENCY_FLASH" - if a["sev"]>=2: return "CRITICAL_BREATHE" - return "WARNING_BREATHE" +def rgb_mode_for(request_id,severity): + if severity<=0: return "OFF" + color=RGB_OBJECT_COLORS[request_id.split("|",1)[0]] + if severity>=3: return "EMERGENCY_FLASH" + return f"{color}_{'FAST' if severity>=2 else 'SLOW'}_BREATHE" def rgb_emergency(on): global _last_rgb @@ -503,7 +508,7 @@ class DisplayController: def request(self,request_id,severity,title,l2="",l3="",l4="",metadata=None): now=time.monotonic(); priority=display_priority(request_id) request={"id":request_id,"priority":priority,"severity":severity, - "rgb_mode":rgb_mode_for({"sev":severity}),"title":title[:21], + "rgb_mode":rgb_mode_for(request_id,severity),"title":title[:21], "l2":l2[:21],"l3":l3[:21],"l4":l4[:21],"active":True, "last_changed":now,"metadata":metadata or {}} with self.lock: @@ -517,7 +522,9 @@ class DisplayController: ("priority","severity","rgb_mode","title","l2","l3","l4","metadata")) if not changed: request["last_changed"]=current["last_changed"] self.requests[request_id]=request - if self.batch_depth==0: self._arbitrate_locked() + if self.batch_depth==0: + self._arbitrate_locked() + self._sync_rgb_locked() def clear(self,request_id): if request_id=="NORMAL_HOME": return @@ -528,6 +535,7 @@ class DisplayController: self._arbitrate_locked() if self.current_oled_page_id not in self.requests: self._show_page_locked(self.current_owner_id) + else: self._sync_rgb_locked() def begin_update(self): with self.lock: self.batch_depth+=1 @@ -540,6 +548,7 @@ class DisplayController: self._arbitrate_locked() if self.current_oled_page_id not in self.requests: self._show_page_locked(self.current_owner_id) + else: self._sync_rgb_locked() def _arbitrate_locked(self): current=self.requests.get(self.current_owner_id) @@ -567,12 +576,23 @@ class DisplayController: 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)) + def _sync_rgb_locked(self): + # Alert pages show that alert's object color. HOME keeps the highest- + # priority active owner visible instead of briefly switching RGB off. + rgb_request=self.requests.get(self.current_oled_page_id) + if self.current_oled_page_id=="NORMAL_HOME": + 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"] + def refresh(self): with self.lock: alerts=self._ordered_alerts_locked() diff --git a/config/pigway-pi-control.conf b/config/pigway-pi-control.conf index 9b43e61..0961665 100644 --- a/config/pigway-pi-control.conf +++ b/config/pigway-pi-control.conf @@ -139,24 +139,29 @@ wifi_weak_seconds = 30 [led] -# DisplayController 按集中定义的 priority 仲裁 owner,RGB 使用该 owner 的 severity。 -# OLED 独立轮播主页与全部当前告警页;切页不会关闭、重启或改变 RGB: -# Warning (1) = 黄色慢呼吸 -# Critical (2) = 红色快呼吸 +# DisplayController 按集中定义的 priority 仲裁 owner。告警页显示时 RGB 同步显示 +# 该页对象;主页使用最高优先级 active owner。颜色表示对象,速度表示严重等级: +# CPU/供电/降频 = 黄色 +# 温度 = 红色 +# 内存 = 紫色 +# 磁盘 = 白色 +# 网络/Wi-Fi = 蓝色 +# 服务/进程 = 青色 +# Warning (1) = 对象颜色慢呼吸 +# Critical (2) = 对象颜色快呼吸 # Emergency (3) = 纯红快速频闪 # NORMAL_HOME 为 owner 时 RGB OFF。 # # 该旧版 MCU 由内置灯效产生呼吸。仅在模式变化时按 # speed -> color -> effect 写入,禁止软件 PWM 和循环帧日志。 -# 卖家协议:speed 1=慢、2=中、3=快;color 0=红、3=黄。 +# 卖家协议:speed 1=慢、2=中、3=快;color 0..6=红/绿/蓝/黄/紫/青/白。 warning_speed = 1 -warning_color = 3 critical_speed = 3 -critical_color = 0 write_delay_ms = 10 emergency_flash_ms = 300 -# v3.3.0 之前的 brightness、*_breathe_seconds、rgb_update_ms 和 *_r/g/b +# v3.3.0 之前的 brightness、*_breathe_seconds、rgb_update_ms、warning_color、 +# critical_color 和 *_r/g/b # 可以继续留在现有配置中;硬件灯效引擎会安全忽略它们。