refactor: separate monitoring from hardware plugins
This commit is contained in:
+30
-173
@@ -1,197 +1,54 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
FORCE_I2C_CONFLICT=0
|
||||
NO_START=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--force-i2c-conflict) FORCE_I2C_CONFLICT=1 ;;
|
||||
-h|--help)
|
||||
echo "Usage: sudo ./install.sh [--force-i2c-conflict]"
|
||||
echo " --force-i2c-conflict Continue even when another process owns /dev/i2c-1."
|
||||
exit 0
|
||||
;;
|
||||
*) echo "ERROR: unknown option: $arg" >&2; exit 2 ;;
|
||||
--no-start) NO_START=1 ;;
|
||||
-h|--help) echo 'Usage: sudo ./install.sh [--no-start]'; exit 0 ;;
|
||||
*) echo "ERROR: unknown option: $arg (hardware is installed separately)" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
for required in app/pigway_pi_control.py app/pigway_pi_control_api.py app/oled_font_5x7.bin web/index.html config/pigway-pi-control.conf systemd/pigway-pi-control.service systemd/pigway-pi-control-api.service; do
|
||||
if [ ! -f "$required" ]; then
|
||||
echo "ERROR: required file missing: $required" >&2
|
||||
exit 1
|
||||
fi
|
||||
for required in app/pigway_pi_control.py app/plugin_api.py app/pigway_pi_control_api.py web/index.html config/pigway-pi-control.conf systemd/pigway-pi-control.service systemd/pigway-pi-control-api.service; do
|
||||
[ -f "$required" ] || { echo "ERROR: required file missing: $required" >&2; exit 1; }
|
||||
done
|
||||
|
||||
echo "[1/8] 检查 Raspberry Pi I2C..."
|
||||
[ -e /dev/i2c-1 ] || { echo "错误:/dev/i2c-1 不存在。先 sudo raspi-config -> Interface Options -> I2C -> Enable,然后重启。"; exit 1; }
|
||||
|
||||
# A running copy of this project is expected during an upgrade. Any other
|
||||
# process holding the shared bus could race OLED/MCU writes, so report it and
|
||||
# let the user decide how that process should be managed.
|
||||
i2c_conflict_pids() {
|
||||
local allowed_pid pid
|
||||
allowed_pid="$(systemctl show -p MainPID --value pigway-pi-control.service 2>/dev/null || true)"
|
||||
for pid in $(fuser /dev/i2c-1 2>/dev/null || true); do
|
||||
case "$pid" in
|
||||
''|*[!0-9]*) continue ;;
|
||||
esac
|
||||
[ "$pid" = "$$" ] && continue
|
||||
[ "$pid" = "$allowed_pid" ] && continue
|
||||
kill -0 "$pid" 2>/dev/null && echo "$pid"
|
||||
done
|
||||
}
|
||||
|
||||
I2C_CONFLICTS="$(i2c_conflict_pids)"
|
||||
if [ -n "$I2C_CONFLICTS" ]; then
|
||||
if [ "$FORCE_I2C_CONFLICT" -eq 1 ]; then
|
||||
echo "WARNING: /dev/i2c-1 is already in use by another process:" >&2
|
||||
else
|
||||
echo "ERROR: /dev/i2c-1 is already in use by another process:" >&2
|
||||
fi
|
||||
while read -r pid; do
|
||||
ps -p "$pid" -o pid=,user=,comm=,args= >&2 || true
|
||||
done <<< "$I2C_CONFLICTS"
|
||||
if [ "$FORCE_I2C_CONFLICT" -eq 1 ]; then
|
||||
echo "WARNING: continuing because --force-i2c-conflict was specified; no conflicting process will be stopped." >&2
|
||||
else
|
||||
echo "Stop or reconfigure the conflicting OLED/LED controller, then run the installer again." >&2
|
||||
echo "If concurrent control is intentional, rerun with --force-i2c-conflict." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[2/8] 等待 APT/DPKG..."
|
||||
for i in $(seq 1 60); do
|
||||
if ! fuser /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock /var/cache/apt/archives/lock >/dev/null 2>&1; then break; fi
|
||||
[ "$i" = 60 ] && { echo "APT 锁等待超时,请稍后重试。"; exit 1; }
|
||||
echo "软件包管理器忙,等待 5 秒... ($i/60)"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
echo "[3/8] 安装依赖..."
|
||||
apt-get update
|
||||
apt-get install -y python3 python3-pil python3-smbus2 i2c-tools
|
||||
|
||||
echo "[4/8] 检查硬件地址..."
|
||||
SCAN="$(i2cdetect -y 1)"; echo "$SCAN"
|
||||
echo "$SCAN" | grep -qi '3c' || echo "警告:未扫描到 OLED 0x3C"
|
||||
echo "$SCAN" | grep -qi '0d' || echo "警告:未扫描到控制 MCU 0x0D"
|
||||
|
||||
echo "[5/8] 安装程序与配置..."
|
||||
mkdir -p /usr/local/share/pigway-pi-control
|
||||
install -m 0644 app/oled_font_5x7.bin /usr/local/share/pigway-pi-control/oled_font_5x7.bin
|
||||
[ "$(id -u)" -eq 0 ] || { echo 'ERROR: run installer as root' >&2; exit 1; }
|
||||
# The monitor has no I2C, GPIO, Pillow, smbus2 or cooling-board requirement.
|
||||
command -v python3 >/dev/null || { apt-get update; apt-get install -y python3; }
|
||||
mkdir -p /usr/local/share/pigway-pi-control/web
|
||||
install -m 0644 app/plugin_api.py /usr/local/share/pigway-pi-control/plugin_api.py
|
||||
install -m 0644 web/index.html /usr/local/share/pigway-pi-control/web/index.html
|
||||
|
||||
install -m 0755 app/pigway_pi_control.py /usr/local/sbin/pigway-pi-control
|
||||
install -m 0755 app/pigway_pi_control_api.py /usr/local/sbin/pigway-pi-control-api
|
||||
|
||||
# Keep the complete existing configuration, including comments and custom sections.
|
||||
# The merge below adds only missing documented options.
|
||||
if [ -f /etc/pigway-pi-control.conf ]; then
|
||||
cp /etc/pigway-pi-control.conf "/etc/pigway-pi-control.conf.bak.$(date +%Y%m%d-%H%M%S)"
|
||||
else
|
||||
install -m 0644 config/pigway-pi-control.conf /etc/pigway-pi-control.conf
|
||||
fi
|
||||
|
||||
# Preserve the existing file while exposing new defaults. Existing values win.
|
||||
python3 - /etc/pigway-pi-control.conf <<'PY'
|
||||
import re,sys
|
||||
# Legacy hardware sections are deliberately preserved for explicit migration,
|
||||
# but ignored by the monitor and never exposed as monitor configuration.
|
||||
python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
|
||||
path=Path(sys.argv[1])
|
||||
text=path.read_text()
|
||||
sections={
|
||||
"device":(("identifier","auto"),("name","auto")),
|
||||
"hosts":(),
|
||||
"led":(
|
||||
("normal_mode","off"),
|
||||
("normal_r",0),("normal_g",0),("normal_b",0),
|
||||
("normal_flash_on_ms",1000),("normal_flash_off_ms",1000),
|
||||
("normal_effect_speed",2),("normal_effect_color","green"),
|
||||
("write_delay_ms",10),
|
||||
("cpu_r",255),("cpu_g",0),("cpu_b",0),
|
||||
("power_r",255),("power_g",40),("power_b",0),
|
||||
("memory_r",255),("memory_g",0),("memory_b",255),
|
||||
("storage_r",255),("storage_g",255),("storage_b",255),
|
||||
("network_r",0),("network_g",0),("network_b",255),
|
||||
("service_r",0),("service_g",255),("service_b",96),
|
||||
("warning_flash_on_ms",1500),("warning_flash_off_ms",1500),
|
||||
("critical_flash_on_ms",750),("critical_flash_off_ms",750),
|
||||
("emergency_flash_on_ms",300),("emergency_flash_off_ms",300),
|
||||
),
|
||||
"api":(("bind","0.0.0.0"),("port",6001),("log_limit",200)),
|
||||
}
|
||||
for section,defaults in sections.items():
|
||||
match=re.search(rf"(?m)^\[{re.escape(section)}\]\s*$",text)
|
||||
if match:
|
||||
next_section=re.search(r"(?m)^\[.+\]\s*$",text[match.end():])
|
||||
end=match.end()+(next_section.start() if next_section else len(text)-match.end())
|
||||
chunk=text[match.end():end]
|
||||
else:
|
||||
text=text.rstrip()+f"\n\n[{section}]\n"
|
||||
match=re.search(rf"(?m)^\[{re.escape(section)}\]\s*$",text)
|
||||
end=len(text); chunk=""
|
||||
missing=[(key,value) for key,value in defaults
|
||||
if not re.search(rf"(?m)^\s*{re.escape(key)}\s*=",chunk)]
|
||||
if missing:
|
||||
note="# Added by installer; existing user values are preserved.\n"
|
||||
if section=="led" and any(key.startswith("normal_") for key,_ in missing):
|
||||
note+=("# Normal RGB: normal_mode = off/solid/flash/flow/breathe/marquee/rainbow/colorful.\n"
|
||||
"# normal_r/g/b = 0..255; normal_flash_*_ms = 50..60000; effect speed = 1..3.\n"
|
||||
"# Effect color = red/green/blue/yellow/purple/cyan/white (flow/breathe only).\n")
|
||||
block=note+"".join(f"{key} = {value}\n" for key,value in missing)
|
||||
text=text[:end].rstrip()+"\n"+block+"\n\n"+text[end:].lstrip("\n")
|
||||
path.write_text(text)
|
||||
import configparser
|
||||
p=Path('/etc/pigway-pi-control.conf');c=configparser.ConfigParser();c.read(p)
|
||||
with p.open('a') as f:
|
||||
for section,values in {'device':{'identifier':'auto','name':'auto'},'hosts':{},'plugins':{},'api':{'bind':'0.0.0.0','port':'6001','log_limit':'200'}}.items():
|
||||
if not c.has_section(section):
|
||||
f.write('\n['+section+']\n'+''.join(k+' = '+v+'\n' for k,v in values.items()))
|
||||
PY
|
||||
if [ ! -s /etc/pigway-pi-control-api.token ]; then
|
||||
umask 077
|
||||
python3 - <<'PY' > /etc/pigway-pi-control-api.token
|
||||
import secrets
|
||||
print(secrets.token_urlsafe(32))
|
||||
PY
|
||||
(umask 077; python3 -c 'import secrets; print(secrets.token_urlsafe(32))' > /etc/pigway-pi-control-api.token)
|
||||
fi
|
||||
chmod 0600 /etc/pigway-pi-control-api.token
|
||||
install -m 0644 systemd/pigway-pi-control.service /etc/systemd/system/pigway-pi-control.service
|
||||
install -m 0644 systemd/pigway-pi-control-api.service /etc/systemd/system/pigway-pi-control-api.service
|
||||
|
||||
echo "[6/8] 启用/重启服务..."
|
||||
install -m 0644 systemd/pigway-pi-control.service /etc/systemd/system/
|
||||
install -m 0644 systemd/pigway-pi-control-api.service /etc/systemd/system/
|
||||
systemctl daemon-reload
|
||||
systemctl enable pigway-pi-control.service pigway-pi-control-api.service >/dev/null
|
||||
systemctl restart pigway-pi-control.service
|
||||
systemctl restart pigway-pi-control-api.service
|
||||
|
||||
echo "[7/8] 安装后自检..."
|
||||
FAIL=0
|
||||
check() { local label="$1"; shift; if "$@"; then printf " %-30s PASS\n" "$label"; else printf " %-30s FAIL\n" "$label"; FAIL=1; fi; }
|
||||
APP=/usr/local/sbin/pigway-pi-control
|
||||
check "Version 3.7.0" grep -Fq 'APP_VERSION="3.7.0"' "$APP"
|
||||
check "OLED physical 128x32" grep -Fq 'W,H=128,32' "$APP"
|
||||
check "OLED MUX 0x1F" grep -Fq '0xA8,0x1F' "$APP"
|
||||
check "OLED pages 0..3" grep -Fq 'cmd(0x22);cmd(0);cmd(3)' "$APP"
|
||||
check "UI CPU/TMP/DSK" grep -Fq 'f"TMP {temp:.1f}C"' "$APP"
|
||||
check "UI IP4/IP6" grep -Fq 'ip_label' "$APP"
|
||||
check "UI right column x=72" grep -Fq 'draw_text_5x7(pix,72,y,right[row])' "$APP"
|
||||
if [ "$FORCE_I2C_CONFLICT" -eq 1 ]; then
|
||||
printf " %-30s WARN (forced)\n" "I2C owner conflict"
|
||||
else
|
||||
no_other_i2c_owner() { [ -z "$(i2c_conflict_pids)" ]; }
|
||||
check "No other I2C owner" no_other_i2c_owner
|
||||
if [ "$NO_START" -eq 0 ]; then
|
||||
systemctl enable pigway-pi-control.service pigway-pi-control-api.service >/dev/null
|
||||
systemctl restart pigway-pi-control.service pigway-pi-control-api.service
|
||||
systemctl is-active --quiet pigway-pi-control.service
|
||||
systemctl is-active --quiet pigway-pi-control-api.service
|
||||
fi
|
||||
check "Service running" systemctl is-active --quiet pigway-pi-control.service
|
||||
check "API/Web service running" systemctl is-active --quiet pigway-pi-control-api.service
|
||||
check "API/Web version 3.7.0" grep -Fq 'APP_VERSION="3.7.0"' /usr/local/sbin/pigway-pi-control-api
|
||||
check "Web UI installed" test -s /usr/local/share/pigway-pi-control/web/index.html
|
||||
check "P0 health monitor" grep -Fq 'throttled_state' "$APP"
|
||||
check "Diagnostic command" grep -Fq -- '--diagnose' "$APP"
|
||||
if [ "$FAIL" -ne 0 ]; then echo "错误:安装后自检未全部通过。"; exit 1; fi
|
||||
echo "[8/8] 状态..."
|
||||
|
||||
|
||||
systemctl --no-pager --full status pigway-pi-control.service || true
|
||||
systemctl --no-pager --full status pigway-pi-control-api.service || true
|
||||
echo
|
||||
echo "完成。程序:/usr/local/sbin/pigway-pi-control"
|
||||
echo "配置:/etc/pigway-pi-control.conf"
|
||||
echo "Web/API:http://$(hostname -I | awk '{print $1}'):$(awk -F= '/^port[[:space:]]*=/{gsub(/[[:space:]]/,"",$2);print $2;exit}' /etc/pigway-pi-control.conf)"
|
||||
echo "配置写入 token:sudo cat /etc/pigway-pi-control-api.token"
|
||||
echo 'Monitor installed. Hardware plugins are installed and connected separately.'
|
||||
echo 'Config: /etc/pigway-pi-control.conf'
|
||||
echo 'API token: sudo cat /etc/pigway-pi-control-api.token'
|
||||
|
||||
Reference in New Issue
Block a user