feat: make plugin integration opt-in

This commit is contained in:
way
2026-09-27 22:28:05 +08:00
parent 4367d0b9d0
commit 50a0f5690f
7 changed files with 58 additions and 10 deletions
+7 -2
View File
@@ -6,6 +6,8 @@
```bash ```bash
sudo ./install.sh sudo ./install.sh
# 允许监控接入(默认关闭):
sudo ./install.sh --enable-integration
# 只安装,不启动/启用新服务: # 只安装,不启动/启用新服务:
sudo ./install.sh --no-start sudo ./install.sh --no-start
``` ```
@@ -22,11 +24,14 @@ curl --unix-socket /run/pigway-plugins/yahboom-cooling-hat/api.sock http://local
无论是否接入监控,插件都自行采集 CPU、内存、温度、磁盘和主网络状态,OLED 主页显示本机数据,风扇执行本地温控,RGB 采用正常灯效配置,默认全灭。 无论是否接入监控,插件都自行采集 CPU、内存、温度、磁盘和主网络状态,OLED 主页显示本机数据,风扇执行本地温控,RGB 采用正常灯效配置,默认全灭。
温度传感器读取失败时请求风扇全速;停止插件时也请求全速并关闭 OLED/RGB,避免因退出显示程序而取消散热。硬件故障时不能保证执行成功,错误会记录日志。风扇无转速反馈,只报告设置档位,不伪造 RPM。 温度传感器读取失败时请求风扇全速;停止插件时也请求全速并关闭 OLED/RGB,避免因退出显示程序而取消散热。硬件故障时不能保证执行成功,错误会记录日志。风扇无转速反馈,只报告设置档位,不伪造 RPM。
## 接入监控 ## 可选接入监控
安装时可选择允许接入,默认否;非交互安装默认关闭。关闭时不创建 API socket,本机采集、显示和温控继续运行。也可修改 `/etc/pigway-cooling-hat.conf` 的 `[integration] enabled = true/false` 并重启插件。`--disable-integration` 显式关闭。
可选接入 [PIGWay Pi Control 监控平台](https://tea.pigway.com/way/pigway-pi-control),接收服务、进程和系统健康告警。双方独立安装、独立运行;不接入也可正常使用本地功能。 可选接入 [PIGWay Pi Control 监控平台](https://tea.pigway.com/way/pigway-pi-control),接收服务、进程和系统健康告警。双方独立安装、独立运行;不接入也可正常使用本地功能。
监控 Web → 系统配置 → 硬件插件中发现并手动接入。协议是本机 Unix socket 上的 HTTP API,默认不开放 TCP 端口。目录 0750、socket 0660,只有 root 和被明确加入 `pigway-hardware` 组的账户能访问。不要随意授予组成员资格。 [独立 Web 管理中心](https://tea.pigway.com/way/pigway-web-manager) → 系统配置 → 硬件插件中发现并手动接入。协议是本机 Unix socket 上的 HTTP API,默认不开放 TCP 端口。目录 0750、socket 0660,只有 root 和被明确加入 `pigway-hardware` 组的账户能访问。不要随意授予组成员资格。
监控发送完整、带版本和有效期的状态;不发送动画帧或 MCU 指令。插件自己仲裁 OLED 页和 RGB 效果。告警页有页码并轮播主页,相同 RGB 效果不因切页重启。只有一个有效监控来源可以持有显示状态,重复/乱序版本会拒绝。状态超过 10 秒未更新(由客户端声明,允许3–30秒),旧告警失效,OLED 回到本机主页、RGB 回到正常灯效,风扇继续本地温控。主动断开行为相同。监控快照中的 system/network 字段保留 API v1 兼容,但不会覆盖插件的本地数据。采样失败显示 --,不伪装为零。 监控发送完整、带版本和有效期的状态;不发送动画帧或 MCU 指令。插件自己仲裁 OLED 页和 RGB 效果。告警页有页码并轮播主页,相同 RGB 效果不因切页重启。只有一个有效监控来源可以持有显示状态,重复/乱序版本会拒绝。状态超过 10 秒未更新(由客户端声明,允许3–30秒),旧告警失效,OLED 回到本机主页、RGB 回到正常灯效,风扇继续本地温控。主动断开行为相同。监控快照中的 system/network 字段保留 API v1 兼容,但不会覆盖插件的本地数据。采样失败显示 --,不伪装为零。
+6 -6
View File
@@ -1,13 +1,13 @@
1331f33332c6c33ecfc3e91d8734ee3ff7755ac1c2ac337fb2e5c367072c1ea7 README.md 2b17b2e0a9bce7f9af8cb6ab5905e01721dddf7513ffd1dfc8c73120dfccbe23 README.md
30646b607d69dcbddbd5b41119a22f63ffb8de22929d1d0f9ff3715326a5bca6 install.sh 93bde984cdce2326601ac808fe55022fe68d9d624b9dcf4ff8b023613418b3c4 install.sh
cd6bb5137dd5922e48866f6daf4b949a16f8fad321e77addd5b4f75181bc4248 uninstall.sh cd6bb5137dd5922e48866f6daf4b949a16f8fad321e77addd5b4f75181bc4248 uninstall.sh
1c7836f80b0845fbc5c4b8570b12d736e2a3c442b1ceb5d35d48cdd518e4ad50 app/drivers/yahboom.py 1c7836f80b0845fbc5c4b8570b12d736e2a3c442b1ceb5d35d48cdd518e4ad50 app/drivers/yahboom.py
215a62acc0a5c87303d6c52c0a2585b87dfb5cdbafbe9e9e733523d48a6a9812 app/migrate.py 215a62acc0a5c87303d6c52c0a2585b87dfb5cdbafbe9e9e733523d48a6a9812 app/migrate.py
72c46162c33f9587c6ccb01ad53fbaaf4ecec8cd21014b909e87e8707e253e36 app/oled_font_5x7.bin 72c46162c33f9587c6ccb01ad53fbaaf4ecec8cd21014b909e87e8707e253e36 app/oled_font_5x7.bin
1803eebf7917291587204db85f74e2009e87f3a7b27a194b1aba245aa49b442b app/runtime.py 1803eebf7917291587204db85f74e2009e87f3a7b27a194b1aba245aa49b442b app/runtime.py
e5153007a3562dc48468c6cee74de55de35844fc1e0e4d0cf00feb696991e14f app/service.py af4a97339c805b5471149da29699d465cb76b20a8f1da7ad8af8e32d4a2fc827 app/service.py
0bd9cf86e5c8c5b36ded2dc957a38ad50ec23ccc69ba91c9968d3f76391ef1c9 app/settings.py 413548a4b38ac3ad32712c92df5e7cd4e9ef2603872ca201d3e4df8c50dbad4a app/settings.py
0e1cd04786652bb0a54589b43050937c1bdc6cf1ec6a549d611cf7229c29761a config/pigway-cooling-hat.conf 9bcac51c4e3a3d409885a3041ac121a20a48596bc47ee769c7af0b8f3772fa8d config/pigway-cooling-hat.conf
c373cdde2b937bbc58d4b227f3a1d2923f0816c13f1e34548ed1f3b26c101ed5 systemd/pigway-cooling-hat.service c373cdde2b937bbc58d4b227f3a1d2923f0816c13f1e34548ed1f3b26c101ed5 systemd/pigway-cooling-hat.service
73b7922be93bf57169589755137dc5ec7ec7c8059c85c2bf2ee53a344e534d7e docs/HARDWARE_PLUGIN_API.md e3bf3e7070ea62c0526791bed922688c3fb49fee36ca9c375d080119968feb72 docs/HARDWARE_PLUGIN_API.md
7ca33e7a4361673d153b5e6212447d53091e223ee05ec24411083d6c052f2894 app/local_state.py 7ca33e7a4361673d153b5e6212447d53091e223ee05ec24411083d6c052f2894 app/local_state.py
+11 -1
View File
@@ -42,7 +42,7 @@ class Handler(BaseHTTPRequestHandler):
if self.path=='/v1/descriptor':self.send(200,descriptor()) if self.path=='/v1/descriptor':self.send(200,descriptor())
elif self.path=='/v1/status':self.send(200,c.status()) elif self.path=='/v1/status':self.send(200,c.status())
elif self.path=='/v1/config': elif self.path=='/v1/config':
with c.lock:self.send(200,{'config':c.config,'schema':descriptor()['schema'],'notes':descriptor()['notes']}) with c.lock:self.send(200,{'config':{k:v for k,v in c.config.items() if k!='integration'},'schema':{k:v for k,v in descriptor()['schema'].items() if k!='integration'},'notes':descriptor()['notes']})
else:self.send(404,{'error':'not found'}) else:self.send(404,{'error':'not found'})
def do_PUT(self): def do_PUT(self):
@@ -53,6 +53,7 @@ class Handler(BaseHTTPRequestHandler):
elif self.path=='/v1/config': elif self.path=='/v1/config':
updates=validated(body.get('updates',{})) updates=validated(body.get('updates',{}))
if not updates:raise ValueError('no configuration updates') if not updates:raise ValueError('no configuration updates')
if 'integration' in updates:raise ValueError('change integration locally and restart the plugin')
with c.lock: with c.lock:
value={s:dict(v) for s,v in c.config.items()} value={s:dict(v) for s,v in c.config.items()}
for section,fields in updates.items():value[section].update(fields) for section,fields in updates.items():value[section].update(fields)
@@ -62,6 +63,7 @@ class Handler(BaseHTTPRequestHandler):
backup=path.with_name(path.name+'.bak.'+str(time.time_ns())) backup=path.with_name(path.name+'.bak.'+str(time.time_ns()))
backup.write_bytes(path.read_bytes());backup.chmod(0o600) backup.write_bytes(path.read_bytes());backup.chmod(0o600)
save(path,value);c.config=value save(path,value);c.config=value
log('CONFIG_UPDATED',sections=','.join(updates));self.send(200,{'ok':True}) log('CONFIG_UPDATED',sections=','.join(updates));self.send(200,{'ok':True})
else:self.send(404,{'error':'not found'}) else:self.send(404,{'error':'not found'})
except (ValueError,TypeError,KeyError) as exc:self.send(400,{'error':str(exc)}) except (ValueError,TypeError,KeyError) as exc:self.send(400,{'error':str(exc)})
@@ -104,6 +106,14 @@ def main():
controller.stop() controller.stop()
controller.driver=None controller.driver=None
path.unlink(missing_ok=True) path.unlink(missing_ok=True)
if config['integration']['enabled']!='true':
stopped=threading.Event()
signal.signal(signal.SIGTERM,lambda *_:stopped.set())
signal.signal(signal.SIGINT,lambda *_:stopped.set())
log('START',plugin=descriptor()['id'],integration=False,available=controller.driver is not None)
try:stopped.wait()
finally:controller.stop();log('STOP',i2c_errors=controller.i2c_errors)
return
try: try:
with Server(str(path),Handler) as server: with Server(str(path),Handler) as server:
path.chmod(0o660);server.controller=controller;server.config_path=Path(args.config) path.chmod(0o660);server.controller=controller;server.config_path=Path(args.config)
+1
View File
@@ -17,6 +17,7 @@ def option(section,key,default,zh,en,kind="number",minimum=None,maximum=None,cho
for key,value,label in (("start",50,"启动温度"),("level2",53,"二档温度"),("level3",56,"三档温度"),("level4",59,"四档温度"),("max",62,"全速温度"),("hysteresis",1.5,"降档回差")): for key,value,label in (("start",50,"启动温度"),("level2",53,"二档温度"),("level3",56,"三档温度"),("level4",59,"四档温度"),("max",62,"全速温度"),("hysteresis",1.5,"降档回差")):
option("fan",key,value,label,key.replace('_',' '),minimum=0,maximum=100 if key!="hysteresis" else 20,unit="°C") option("fan",key,value,label,key.replace('_',' '),minimum=0,maximum=100 if key!="hysteresis" else 20,unit="°C")
option("integration","enabled","false","允许监控接入(重启生效)","Allow monitor integration (restart required)","enum",choices=["false","true"])
option("oled","refresh_seconds",1,"刷新周期","Refresh interval",minimum=.2,maximum=10,unit="s") option("oled","refresh_seconds",1,"刷新周期","Refresh interval",minimum=.2,maximum=10,unit="s")
option("oled","page_seconds",10,"轮播周期","Page interval",minimum=1,maximum=120,unit="s") option("oled","page_seconds",10,"轮播周期","Page interval",minimum=1,maximum=120,unit="s")
option("rgb","alert_mode","breathe","告警模式","Alert mode","enum",choices=["breathe","flash"]) option("rgb","alert_mode","breathe","告警模式","Alert mode","enum",choices=["breathe","flash"])
+5
View File
@@ -109,3 +109,8 @@ memory_breathe_color = purple
storage_breathe_color = white storage_breathe_color = white
network_breathe_color = blue network_breathe_color = blue
service_breathe_color = cyan service_breathe_color = cyan
[integration]
# 默认不开启接入 API;true 允许本机监控通过 Unix socket 接入。修改后重启插件。
# Local features work independently. Restart after changing this setting.
enabled = false
+10
View File
@@ -79,3 +79,13 @@ fallback; actual execution still depends on functioning hardware.
Stopping a monitor, pausing its link, or unlinking a plugin does not stop the Stopping a monitor, pausing its link, or unlinking a plugin does not stop the
plugin service. No API/Web service has a systemd Wants dependency that starts the plugin service. No API/Web service has a systemd Wants dependency that starts the
other application. Do not run two drivers for the same MCU, GPIO or fan. other application. Do not run two drivers for the same MCU, GPIO or fan.
## Optional listeners
Agent network API defaults to disabled (`[api] enabled=false`) and has no Web UI.
When enabled, every request requires a device bearer token.
Hardware plugin integration defaults to disabled (`[integration] enabled=false`):
no Unix API socket is created, while local telemetry and hardware workers continue.
Change integration locally and restart the plugin. Monitor-to-plugin linkage does
not require the Agent network API. The independent Web Manager stores registered
device addresses and tokens; it is not implicitly a monitored device.
+18 -1
View File
@@ -3,11 +3,14 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
NO_START=0 NO_START=0
IMPORT='' IMPORT=''
INTEGRATION=''
while [ "$#" -gt 0 ]; do while [ "$#" -gt 0 ]; do
case "$1" in case "$1" in
--enable-integration) INTEGRATION=true; shift ;;
--disable-integration) INTEGRATION=false; shift ;;
--no-start) NO_START=1; shift ;; --no-start) NO_START=1; shift ;;
--import-monitor-config) [ "$#" -ge 2 ] || exit 2; IMPORT="$2"; shift 2 ;; --import-monitor-config) [ "$#" -ge 2 ] || exit 2; IMPORT="$2"; shift 2 ;;
-h|--help) echo 'Usage: sudo ./install.sh [--no-start] [--import-monitor-config /etc/pigway-pi-control.conf]'; exit 0 ;; -h|--help) echo 'Usage: sudo ./install.sh [--no-start] [--enable-integration|--disable-integration] [--import-monitor-config /etc/pigway-pi-control.conf]'; exit 0 ;;
*) echo "ERROR: unknown option: $1" >&2; exit 2 ;; *) echo "ERROR: unknown option: $1" >&2; exit 2 ;;
esac esac
done done
@@ -24,6 +27,13 @@ done
python3 -c 'import smbus2, PIL' 2>/dev/null || { apt-get update; apt-get install -y python3 python3-smbus2 python3-pil; } python3 -c 'import smbus2, PIL' 2>/dev/null || { apt-get update; apt-get install -y python3 python3-smbus2 python3-pil; }
# Validate a possible migration before changing installed files. # Validate a possible migration before changing installed files.
if [ -n "$IMPORT" ]; then PYTHONDONTWRITEBYTECODE=1 python3 app/migrate.py "$IMPORT" --check; fi if [ -n "$IMPORT" ]; then PYTHONDONTWRITEBYTECODE=1 python3 app/migrate.py "$IMPORT" --check; fi
if [ -z "$INTEGRATION" ]; then
INTEGRATION=false
if [ -t 0 ]; then
read -r -p 'Allow local monitor integration? [y/N] ' answer
case "$answer" in y|Y|yes|YES) INTEGRATION=true ;; esac
fi
fi
getent group pigway-hardware >/dev/null || groupadd --system pigway-hardware getent group pigway-hardware >/dev/null || groupadd --system pigway-hardware
mkdir -p /usr/local/lib/pigway-cooling-hat/drivers mkdir -p /usr/local/lib/pigway-cooling-hat/drivers
install -m 0644 app/*.py app/oled_font_5x7.bin /usr/local/lib/pigway-cooling-hat/ install -m 0644 app/*.py app/oled_font_5x7.bin /usr/local/lib/pigway-cooling-hat/
@@ -35,6 +45,13 @@ if [ -n "$IMPORT" ]; then
cp /etc/pigway-cooling-hat.conf "/etc/pigway-cooling-hat.conf.bak.$(date +%Y%m%d-%H%M%S)" cp /etc/pigway-cooling-hat.conf "/etc/pigway-cooling-hat.conf.bak.$(date +%Y%m%d-%H%M%S)"
PYTHONDONTWRITEBYTECODE=1 python3 app/migrate.py "$IMPORT" --output /etc/pigway-cooling-hat.conf PYTHONDONTWRITEBYTECODE=1 python3 app/migrate.py "$IMPORT" --output /etc/pigway-cooling-hat.conf
fi fi
INTEGRATION="$INTEGRATION" PYTHONDONTWRITEBYTECODE=1 python3 - <<'PYCFG'
import os,sys
from pathlib import Path
sys.path.insert(0,'app')
from settings import load,save
p=Path('/etc/pigway-cooling-hat.conf');c=load(p);c['integration']['enabled']=os.environ['INTEGRATION'];save(p,c)
PYCFG
install -m 0644 systemd/pigway-cooling-hat.service /etc/systemd/system/ install -m 0644 systemd/pigway-cooling-hat.service /etc/systemd/system/
systemctl daemon-reload systemctl daemon-reload
if [ "$NO_START" -eq 0 ]; then if [ "$NO_START" -eq 0 ]; then