fix: redesign RGB alert engine
This commit is contained in:
@@ -67,7 +67,11 @@ CHECK STORAGE
|
||||
- 主循环异常计数
|
||||
- I²C相关异常计数基础字段
|
||||
|
||||
### 4. 结构化事件日志
|
||||
### 4. RGB 告警引擎
|
||||
|
||||
RGB 与 OLED 页面完全解耦,只使用所有 active alert 的最高 severity。Warning 为黄色 3 秒呼吸,Critical 为橙红色 1.5 秒呼吸,Emergency 为纯红快速频闪;无 active alert 时 OFF。动画不写循环日志,模式切换仅记录一次 `RGB_MODE`。
|
||||
|
||||
### 5. 结构化事件日志
|
||||
|
||||
日志继续交给 systemd journal,不额外制造长期 `.log` 文件。
|
||||
|
||||
@@ -104,7 +108,7 @@ journalctl -u pigway-pi-control -b --no-pager
|
||||
journalctl -u pigway-pi-control --no-pager | grep 'event=POWER'
|
||||
```
|
||||
|
||||
### 5. 诊断命令
|
||||
### 6. 诊断命令
|
||||
|
||||
```bash
|
||||
sudo /usr/local/sbin/pigway-pi-control --diagnose
|
||||
@@ -116,7 +120,7 @@ sudo /usr/local/sbin/pigway-pi-control --diagnose
|
||||
|
||||
RC2 保留 v3.2.0 的 RGB MCU 寄存器写入顺序;独立闪烁时钟使用所有有效告警的最高 severity,不受 OLED 页面及网络/watch 检查等待影响。服务恢复后自动切换到下一有效告警,没有告警时 OFF。
|
||||
|
||||
### 6. 关机收尾
|
||||
### 7. 关机收尾
|
||||
|
||||
收到 systemd SIGTERM / 系统关机时:
|
||||
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
192d29003ffd867d440753c1903d194a51ec9c2cadbc5442d0e8d1977364217d README.md
|
||||
55fe2917b92a896fb346fb3c9c515fafcf47d7ea095c0d92dfea66a7b3edfc19 README.md
|
||||
72c46162c33f9587c6ccb01ad53fbaaf4ecec8cd21014b909e87e8707e253e36 app/oled_font_5x7.bin
|
||||
1445d390eef0a66c14d827f8468064f5a09ae2f005e04a636ae38f77dd0bb5d7 app/pigway_pi_control.py
|
||||
7d64d7fcf2b17580b4565b99e290a85e4317dbf2cc71224adcacb56489fcf1b3 config/pigway-pi-control.conf
|
||||
75138ce957a501dc34bf2d886476b92e897d16d42c06ccbffda27524f164cce4 app/pigway_pi_control.py
|
||||
fbd8829929bc755cef6beeded475ac5eaf1a5b9152b4294bb8d169e224e3bc9c config/pigway-pi-control.conf
|
||||
4c2816da08378f2927c746b70ecfa7d5312e1730c08727cd89e8c2801e707054 install.sh
|
||||
81aea0a89b298b2512fcb9bf6418618ba7a66474d425390cf95a0128c4093474 systemd/pigway-pi-control.service
|
||||
a3d74973cafbabaf58a660a8c83d28347d1c06d9fd2dab3ecfce5b7607f6fee2 uninstall.sh
|
||||
|
||||
+43
-18
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import time,socket,subprocess,configparser,signal,re,errno,threading,argparse
|
||||
import time,socket,subprocess,configparser,signal,re,errno,threading,argparse,math
|
||||
from pathlib import Path
|
||||
APP_VERSION="3.3.0"
|
||||
from smbus2 import SMBus
|
||||
@@ -236,7 +236,7 @@ OLED_REFRESH=cf("timing","oled_refresh_interval",1.0)
|
||||
PAGE_INTERVAL=cf("timing","page_interval",10.0)
|
||||
NETWORK_INTERVAL=cf("timing","network_interval",5.0)
|
||||
SERVICE_INTERVAL=cf("timing","service_interval",5.0)
|
||||
HEALTH_INTERVAL=cf("timing","health_interval",5.0)
|
||||
HEALTH_INTERVAL=cf("timing","health_interval",1.0)
|
||||
|
||||
CPU_HIGH=cf("alerts","cpu_high",90.0)
|
||||
CPU_RECOVER=cf("alerts","cpu_recover",75.0)
|
||||
@@ -259,9 +259,13 @@ ROW_Y=[cf("display","row1_y",0,int),cf("display","row2_y",8,int),cf("display","r
|
||||
DARK_PWR=cf("dark_mode","pi_pwr_led",True,lambda x: str(x).strip().lower() in ("off","0","false","no"))
|
||||
DARK_ACT=cf("dark_mode","pi_act_led",True,lambda x: str(x).strip().lower() in ("off","0","false","no"))
|
||||
LED_BRIGHT=cf("led","brightness",10,int)
|
||||
LED_WARN_ON=cf("led","warning_on_ms",300,int); LED_WARN_OFF=cf("led","warning_off_ms",2700,int)
|
||||
LED_CRIT_ON=cf("led","critical_on_ms",400,int); LED_CRIT_OFF=cf("led","critical_off_ms",1100,int)
|
||||
LED_EMERG_ON=cf("led","emergency_on_ms",400,int); LED_EMERG_OFF=cf("led","emergency_off_ms",400,int)
|
||||
WARNING_BREATHE_SECONDS=cf("led","warning_breathe_seconds",3.0)
|
||||
CRITICAL_BREATHE_SECONDS=cf("led","critical_breathe_seconds",1.5)
|
||||
EMERGENCY_FLASH_MS=cf("led","emergency_flash_ms",300,int)
|
||||
RGB_UPDATE_MS=cf("led","rgb_update_ms",40,int)
|
||||
RGB_WARNING=(cf("led","warning_r",255,int),cf("led","warning_g",255,int),cf("led","warning_b",0,int))
|
||||
RGB_CRITICAL=(cf("led","critical_r",255,int),cf("led","critical_g",72,int),cf("led","critical_b",0,int))
|
||||
RGB_EMERGENCY=(cf("led","emergency_r",255,int),cf("led","emergency_g",0,int),cf("led","emergency_b",0,int))
|
||||
|
||||
def check_watch(w):
|
||||
typ,label,target=w
|
||||
@@ -357,16 +361,29 @@ def make_alerts(cpu,temp,mem,disk,sample_elapsed=0.0):
|
||||
a.sort(key=lambda x:x["sev"],reverse=True)
|
||||
return a
|
||||
|
||||
def led_for(a):
|
||||
# Color = fault object. Blink frequency = severity. All alerts blink.
|
||||
if not a:
|
||||
rgb_off(); return
|
||||
if a["sev"]>=3: on_ms,off_ms=LED_EMERG_ON,LED_EMERG_OFF
|
||||
elif a["sev"]>=2: on_ms,off_ms=LED_CRIT_ON,LED_CRIT_OFF
|
||||
else: on_ms,off_ms=LED_WARN_ON,LED_WARN_OFF
|
||||
cycle=max(1,on_ms+off_ms)
|
||||
phase=int(time.monotonic()*1000)%cycle
|
||||
rgb(*a["color"]) if phase<on_ms else rgb_off()
|
||||
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 breathe(level_seconds,elapsed):
|
||||
# A full sine-derived ease in/out cycle gives continuous light transitions.
|
||||
phase=(elapsed/max(0.1,level_seconds))%1.0
|
||||
return 0.5-0.5*math.cos(2.0*math.pi*phase)
|
||||
|
||||
def rgb_engine(mode,elapsed):
|
||||
if mode=="OFF":
|
||||
rgb_off()
|
||||
elif mode=="WARNING_BREATHE":
|
||||
level=breathe(WARNING_BREATHE_SECONDS,elapsed)
|
||||
rgb(*(channel*level for channel in RGB_WARNING))
|
||||
elif mode=="CRITICAL_BREATHE":
|
||||
level=breathe(CRITICAL_BREATHE_SECONDS,elapsed)
|
||||
rgb(*(channel*level for channel in RGB_CRITICAL))
|
||||
else:
|
||||
on=int(elapsed*1000/max(50,EMERGENCY_FLASH_MS))%2==0
|
||||
rgb(RGB_EMERGENCY[0] if on else 0,0,0)
|
||||
|
||||
|
||||
def render_home(cpu,temp,mem,disk):
|
||||
@@ -417,21 +434,29 @@ def find_live_alert(key, live):
|
||||
if alert_key(a)==key: return a
|
||||
return None
|
||||
|
||||
# RGB has its own clock so slow network/watch checks cannot stretch blink pulses.
|
||||
# RGB has its own clock and consumes only the highest-severity active alert.
|
||||
# OLED rendering never reads or writes RGB state.
|
||||
led_stop=threading.Event()
|
||||
led_alert=None
|
||||
|
||||
def led_worker():
|
||||
previous_error=None
|
||||
current_mode="OFF"
|
||||
mode_started=time.monotonic()
|
||||
while not led_stop.is_set():
|
||||
try:
|
||||
with bus_lock: led_for(led_alert)
|
||||
new_mode=rgb_mode_for(led_alert)
|
||||
if new_mode!=current_mode:
|
||||
log("INFO","RGB_MODE",old=current_mode,new=new_mode)
|
||||
current_mode=new_mode; mode_started=time.monotonic()
|
||||
# Keep the verified five-write MCU RGB transaction contiguous.
|
||||
with bus_lock: rgb_engine(current_mode,time.monotonic()-mode_started)
|
||||
if previous_error is not None: log("INFO","RGB_RECOVERED")
|
||||
previous_error=None
|
||||
except Exception as e:
|
||||
if repr(e)!=previous_error: log("ERROR","RGB_ERROR",error=repr(e))
|
||||
previous_error=repr(e)
|
||||
led_stop.wait(0.02)
|
||||
led_stop.wait(max(0.02,min(0.05,RGB_UPDATE_MS/1000.0)))
|
||||
|
||||
def cleanup():
|
||||
log("INFO","STOP",loop_errors=agent["loop_errors"],i2c_errors=agent["i2c_errors"])
|
||||
|
||||
@@ -50,8 +50,8 @@ service_interval = 5
|
||||
|
||||
# 本机健康检查周期,单位:秒。
|
||||
# 检查 Raspberry Pi 欠压/降频状态、根文件系统只读状态等。
|
||||
# 推荐:5。
|
||||
health_interval = 5
|
||||
# 推荐:1。当前电源状态必须及时反映到 OLED/RGB;历史状态仅记录一次日志。
|
||||
health_interval = 1
|
||||
|
||||
|
||||
[fan]
|
||||
@@ -139,34 +139,33 @@ wifi_weak_seconds = 30
|
||||
|
||||
|
||||
[led]
|
||||
# RGB 规则:
|
||||
# 正常状态 = 完全熄灭
|
||||
# 颜色 = 哪一类对象发生故障
|
||||
# 闪烁频率 = 严重程度,越严重闪得越快
|
||||
#
|
||||
# 当前颜色语义:
|
||||
# CPU负载 = 黄
|
||||
# 温度/散热 = 红
|
||||
# 内存 = 紫
|
||||
# 磁盘 = 橙
|
||||
# 网络 = 蓝
|
||||
# 服务/进程 = 青
|
||||
# RGB 只由所有 active alert 中最高 severity 决定,与 OLED 当前页面完全无关:
|
||||
# Warning (1) = 黄色慢呼吸
|
||||
# Critical (2) = 橙红色快呼吸
|
||||
# Emergency (3) = 纯红快速频闪
|
||||
# 无 active alert 时 RGB OFF。每次写入明确设置 R/G/B,绝不沿用旧颜色。
|
||||
#
|
||||
# brightness 是 RGB 最大输出百分比(0~100)。
|
||||
# 3颗 RGB 会一起亮,卧室建议 5~15。
|
||||
brightness = 10
|
||||
|
||||
# Warning:慢闪。以下单位均为毫秒。
|
||||
warning_on_ms = 300
|
||||
warning_off_ms = 2700
|
||||
# 旧 warning_on_ms / warning_off_ms / critical_on_ms / critical_off_ms /
|
||||
# emergency_on_ms / emergency_off_ms 仍可保留,RC3 忽略它们以兼容旧配置。
|
||||
warning_breathe_seconds = 3.0
|
||||
critical_breathe_seconds = 1.5
|
||||
emergency_flash_ms = 300
|
||||
rgb_update_ms = 40
|
||||
|
||||
# Critical:中速闪烁。
|
||||
critical_on_ms = 400
|
||||
critical_off_ms = 1100
|
||||
|
||||
# Emergency:快速闪烁。
|
||||
emergency_on_ms = 400
|
||||
emergency_off_ms = 400
|
||||
# RGB 基色(0~255)。安全默认值严格不含蓝色,避免紫色/蓝色残留。
|
||||
warning_r = 255
|
||||
warning_g = 255
|
||||
warning_b = 0
|
||||
critical_r = 255
|
||||
critical_g = 72
|
||||
critical_b = 0
|
||||
emergency_r = 255
|
||||
emergency_g = 0
|
||||
emergency_b = 0
|
||||
|
||||
|
||||
[dark_mode]
|
||||
|
||||
Reference in New Issue
Block a user