refactor: use calibrated RGB alert flashes
This commit is contained in:
@@ -73,7 +73,7 @@ CHECK STORAGE
|
||||
|
||||
### 5. RGB 告警引擎
|
||||
|
||||
RGB 模式由当前 OLED 告警页决定;OLED 位于主页时则使用最高优先级 active owner。颜色表示故障对象:CPU负载、频率、throttling、温度与散热相关为红色,供电/欠压使用实机校准的橙色闪烁,内存为紫色,存储为白色,网络/Wi-Fi 为蓝色,服务/进程为青色。通常 Warning 慢呼吸、Critical 快呼吸、Emergency 使用对象颜色快速频闪;供电告警例外使用 750ms 亮/灭,避开实机偏绿的 MCU 内置黄色。没有 active alert 时 OFF。模式变化时按已校准的 MCU 协议写入,内置颜色的呼吸完全由 MCU 生成,不使用软件 PWM。相邻页面 RGB mode 相同时不关闭、不重启灯效。动画不写循环日志,owner 变化记录 `DISPLAY_OWNER`,实际模式变化才记录 `RGB_MODE`。
|
||||
RGB 模式由当前 OLED 告警页决定;OLED 位于主页时则使用最高优先级 active owner。颜色表示故障对象:CPU负载、频率、throttling、温度与散热相关为红色,供电/欠压为橙色,内存为紫色,存储为白色,网络/Wi-Fi 为蓝色,服务/进程为青色。频率表示严重等级:Warning 为1500ms亮/1500ms灭,Critical 为750ms亮/750ms灭,Emergency 为300ms亮/300ms灭;没有 active alert 时 OFF。每次点亮使用 v3.2.0 已验证的完整 selector/R/G/B 写入顺序,只在亮/灭边沿写 MCU,不做高频软件 PWM。相邻页面 RGB mode 相同时不关闭、不重启节奏。动画不写循环日志,owner 变化记录 `DISPLAY_OWNER`,实际模式变化才记录 `RGB_MODE`。
|
||||
|
||||
### 6. 结构化事件日志
|
||||
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
80ac1d84bb2daff8bb4a1182aad9198726f25624e8d253065e71a4eb062857e2 README.md
|
||||
f3438c9868b0d7bd98c995908186ae21cd6580c1511265b6f7556961a4d78e30 README.md
|
||||
72c46162c33f9587c6ccb01ad53fbaaf4ecec8cd21014b909e87e8707e253e36 app/oled_font_5x7.bin
|
||||
621889210c68d4eba96d79bd8299ae435fe8f2a057ffc77c3cf86fc8c22a3060 app/pigway_pi_control.py
|
||||
403f73cd12ea87a3ac08b447d5438e9ceeef0fbde1e54af40304167bde470f1e config/pigway-pi-control.conf
|
||||
fd70b204dea22eb2f3c60345bc24ffcc286903603e02c7f708ccc301eba2cbc3 app/pigway_pi_control.py
|
||||
57602d3ad8f543c63504942c8fa726c61cf37beb95e58178a8f618ef35a6e51d config/pigway-pi-control.conf
|
||||
4c2816da08378f2927c746b70ecfa7d5312e1730c08727cd89e8c2801e707054 install.sh
|
||||
81aea0a89b298b2512fcb9bf6418618ba7a66474d425390cf95a0128c4093474 systemd/pigway-pi-control.service
|
||||
a3d74973cafbabaf58a660a8c83d28347d1c06d9fd2dab3ecfce5b7607f6fee2 uninstall.sh
|
||||
|
||||
+13
-27
@@ -121,19 +121,6 @@ def rgb_off():
|
||||
mcu(0x07,0x00) # seller closeRGB(): true OFF
|
||||
_last_rgb=("off",)
|
||||
|
||||
def rgb_mode(mode):
|
||||
global _last_rgb
|
||||
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
|
||||
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,0x01)
|
||||
_last_rgb=(mode,)
|
||||
|
||||
def set_pi_led(name,off=True):
|
||||
base=Path("/sys/class/leds")/name
|
||||
try:
|
||||
@@ -261,15 +248,13 @@ FAN_HYST=cf("fan","hysteresis",1.5)
|
||||
ROW_Y=[cf("display","row1_y",0,int),cf("display","row2_y",8,int),cf("display","row3_y",16,int),cf("display","row4_y",24,int)]
|
||||
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"))
|
||||
EMERGENCY_FLASH_MS=cf("led","emergency_flash_ms",300,int)
|
||||
POWER_BLINK_MS=cf("led","power_blink_ms",750,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_CRITICAL_SPEED=max(1,min(3,cf("led","critical_speed",3,int)))
|
||||
RGB_COLOR_VALUES={"RED":0,"GREEN":1,"BLUE":2,"YELLOW":3,"PURPLE":4,"CYAN":5,"WHITE":6}
|
||||
WARNING_FLASH_MS=max(50,cf("led","warning_flash_ms",1500,int))
|
||||
CRITICAL_FLASH_MS=max(50,cf("led","critical_flash_ms",750,int))
|
||||
EMERGENCY_FLASH_MS=max(50,cf("led","emergency_flash_ms",300,int))
|
||||
RGB_STATIC_VALUES={
|
||||
"RED":(0xff,0x00,0x00),"GREEN":(0x00,0xff,0x00),"BLUE":(0x00,0x00,0xff),
|
||||
"YELLOW":(0xff,0xff,0x00),"PURPLE":(0xff,0x00,0xff),"CYAN":(0x00,0xff,0xff),
|
||||
"YELLOW":(0xff,0xff,0x00),"PURPLE":(0xff,0x00,0xff),"CYAN":(0x00,0xff,0x60),
|
||||
"WHITE":(0xff,0xff,0xff),"ORANGE":(0xff,0x28,0x00),
|
||||
}
|
||||
RGB_OBJECT_COLORS={
|
||||
@@ -428,10 +413,9 @@ def update_active_alerts(cpu,temp,mem,disk,sample_elapsed=0.0):
|
||||
def rgb_mode_for(request_id,severity):
|
||||
if severity<=0: return "OFF"
|
||||
alert_type=request_id.split("|",1)[0]
|
||||
if alert_type=="POWER_UNDERVOLT": return "ORANGE_CRITICAL_BLINK"
|
||||
color=RGB_OBJECT_COLORS[alert_type]
|
||||
if severity>=3: return f"{color}_EMERGENCY_FLASH"
|
||||
return f"{color}_{'FAST' if severity>=2 else 'SLOW'}_BREATHE"
|
||||
color="ORANGE" if alert_type=="POWER_UNDERVOLT" else RGB_OBJECT_COLORS[alert_type]
|
||||
level="EMERGENCY" if severity>=3 else "CRITICAL" if severity>=2 else "WARNING"
|
||||
return f"{color}_{level}_FLASH"
|
||||
|
||||
def rgb_flash(mode,on):
|
||||
global _last_rgb
|
||||
@@ -629,18 +613,20 @@ class DisplayController:
|
||||
while not self.stop_event.is_set():
|
||||
try:
|
||||
with self.lock: wanted_mode=self.current_rgb_mode
|
||||
wanted_flash=wanted_mode.endswith(("_EMERGENCY_FLASH","_CRITICAL_BLINK"))
|
||||
wanted_flash=wanted_mode.endswith("_FLASH")
|
||||
if wanted_mode!=applied_mode:
|
||||
with bus_lock:
|
||||
if wanted_mode=="OFF": rgb_off()
|
||||
elif wanted_flash: rgb_flash(wanted_mode,True)
|
||||
else: rgb_mode(wanted_mode)
|
||||
else: raise ValueError(f"unsupported RGB mode: {wanted_mode}")
|
||||
log("INFO","RGB_MODE",old=applied_mode,new=wanted_mode)
|
||||
applied_mode=wanted_mode; animation_started=time.monotonic()
|
||||
applied_flash=applied_mode.endswith(("_EMERGENCY_FLASH","_CRITICAL_BLINK"))
|
||||
applied_flash=applied_mode.endswith("_FLASH")
|
||||
if applied_flash:
|
||||
elapsed=time.monotonic()-animation_started
|
||||
interval=POWER_BLINK_MS if applied_mode=="ORANGE_CRITICAL_BLINK" else EMERGENCY_FLASH_MS
|
||||
if "_EMERGENCY_" in applied_mode: interval=EMERGENCY_FLASH_MS
|
||||
elif "_CRITICAL_" in applied_mode: interval=CRITICAL_FLASH_MS
|
||||
else: interval=WARNING_FLASH_MS
|
||||
on=int(elapsed*1000/max(50,interval))%2==0
|
||||
with bus_lock: rgb_flash(applied_mode,on)
|
||||
if previous_error is not None: log("INFO","RGB_RECOVERED")
|
||||
|
||||
@@ -147,19 +147,17 @@ wifi_weak_seconds = 30
|
||||
# 磁盘 = 白色
|
||||
# 网络/Wi-Fi = 蓝色
|
||||
# 服务/进程 = 青色
|
||||
# Warning (1) = 对象颜色慢呼吸
|
||||
# Critical (2) = 对象颜色快呼吸
|
||||
# Warning (1) = 1500ms 亮 / 1500ms 灭
|
||||
# Critical (2) = 750ms 亮 / 750ms 灭
|
||||
# Emergency (3) = 对象颜色快速频闪
|
||||
# NORMAL_HOME 为 owner 时 RGB OFF。
|
||||
#
|
||||
# 该旧版 MCU 由内置灯效产生呼吸。仅在模式变化时按
|
||||
# speed -> color -> effect 写入,禁止软件 PWM 和循环帧日志。
|
||||
# 卖家协议:speed 1=慢、2=中、3=快;color 0..6=红/绿/蓝/黄/紫/青/白。
|
||||
warning_speed = 1
|
||||
critical_speed = 3
|
||||
# 每次点亮使用 v3.2.0 已验证的 selector -> R -> G -> B 完整写入顺序,
|
||||
# 只在亮/灭边沿访问 MCU,不做高频软件 PWM。
|
||||
write_delay_ms = 10
|
||||
warning_flash_ms = 1500
|
||||
critical_flash_ms = 750
|
||||
emergency_flash_ms = 300
|
||||
power_blink_ms = 750
|
||||
|
||||
# v3.3.0 之前的 brightness、*_breathe_seconds、rgb_update_ms、warning_color、
|
||||
# critical_color 和 *_r/g/b
|
||||
|
||||
Reference in New Issue
Block a user