From 50a0f5690ff8d0a0f37c46eaa5bdbe6104502cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E5=8F=A3=E6=B5=AA=E4=BA=BA?= Date: Sun, 27 Sep 2026 22:28:05 +0800 Subject: [PATCH] feat: make plugin integration opt-in --- README.md | 9 +++++++-- SHA256SUMS | 12 ++++++------ app/service.py | 12 +++++++++++- app/settings.py | 1 + config/pigway-cooling-hat.conf | 5 +++++ docs/HARDWARE_PLUGIN_API.md | 10 ++++++++++ install.sh | 19 ++++++++++++++++++- 7 files changed, 58 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7d037b5..9924775 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ ```bash sudo ./install.sh +# 允许监控接入(默认关闭): +sudo ./install.sh --enable-integration # 只安装,不启动/启用新服务: 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 采用正常灯效配置,默认全灭。 温度传感器读取失败时请求风扇全速;停止插件时也请求全速并关闭 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),接收服务、进程和系统健康告警。双方独立安装、独立运行;不接入也可正常使用本地功能。 -监控 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 兼容,但不会覆盖插件的本地数据。采样失败显示 --,不伪装为零。 diff --git a/SHA256SUMS b/SHA256SUMS index bd74240..27fe524 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,13 +1,13 @@ -1331f33332c6c33ecfc3e91d8734ee3ff7755ac1c2ac337fb2e5c367072c1ea7 README.md -30646b607d69dcbddbd5b41119a22f63ffb8de22929d1d0f9ff3715326a5bca6 install.sh +2b17b2e0a9bce7f9af8cb6ab5905e01721dddf7513ffd1dfc8c73120dfccbe23 README.md +93bde984cdce2326601ac808fe55022fe68d9d624b9dcf4ff8b023613418b3c4 install.sh cd6bb5137dd5922e48866f6daf4b949a16f8fad321e77addd5b4f75181bc4248 uninstall.sh 1c7836f80b0845fbc5c4b8570b12d736e2a3c442b1ceb5d35d48cdd518e4ad50 app/drivers/yahboom.py 215a62acc0a5c87303d6c52c0a2585b87dfb5cdbafbe9e9e733523d48a6a9812 app/migrate.py 72c46162c33f9587c6ccb01ad53fbaaf4ecec8cd21014b909e87e8707e253e36 app/oled_font_5x7.bin 1803eebf7917291587204db85f74e2009e87f3a7b27a194b1aba245aa49b442b app/runtime.py -e5153007a3562dc48468c6cee74de55de35844fc1e0e4d0cf00feb696991e14f app/service.py -0bd9cf86e5c8c5b36ded2dc957a38ad50ec23ccc69ba91c9968d3f76391ef1c9 app/settings.py -0e1cd04786652bb0a54589b43050937c1bdc6cf1ec6a549d611cf7229c29761a config/pigway-cooling-hat.conf +af4a97339c805b5471149da29699d465cb76b20a8f1da7ad8af8e32d4a2fc827 app/service.py +413548a4b38ac3ad32712c92df5e7cd4e9ef2603872ca201d3e4df8c50dbad4a app/settings.py +9bcac51c4e3a3d409885a3041ac121a20a48596bc47ee769c7af0b8f3772fa8d config/pigway-cooling-hat.conf c373cdde2b937bbc58d4b227f3a1d2923f0816c13f1e34548ed1f3b26c101ed5 systemd/pigway-cooling-hat.service -73b7922be93bf57169589755137dc5ec7ec7c8059c85c2bf2ee53a344e534d7e docs/HARDWARE_PLUGIN_API.md +e3bf3e7070ea62c0526791bed922688c3fb49fee36ca9c375d080119968feb72 docs/HARDWARE_PLUGIN_API.md 7ca33e7a4361673d153b5e6212447d53091e223ee05ec24411083d6c052f2894 app/local_state.py diff --git a/app/service.py b/app/service.py index ef8a7bb..e66fa3e 100644 --- a/app/service.py +++ b/app/service.py @@ -42,7 +42,7 @@ class Handler(BaseHTTPRequestHandler): if self.path=='/v1/descriptor':self.send(200,descriptor()) elif self.path=='/v1/status':self.send(200,c.status()) 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'}) def do_PUT(self): @@ -53,6 +53,7 @@ class Handler(BaseHTTPRequestHandler): elif self.path=='/v1/config': updates=validated(body.get('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: value={s:dict(v) for s,v in c.config.items()} 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.write_bytes(path.read_bytes());backup.chmod(0o600) save(path,value);c.config=value + log('CONFIG_UPDATED',sections=','.join(updates));self.send(200,{'ok':True}) else:self.send(404,{'error':'not found'}) except (ValueError,TypeError,KeyError) as exc:self.send(400,{'error':str(exc)}) @@ -104,6 +106,14 @@ def main(): controller.stop() controller.driver=None 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: with Server(str(path),Handler) as server: path.chmod(0o660);server.controller=controller;server.config_path=Path(args.config) diff --git a/app/settings.py b/app/settings.py index b28467c..202ff34 100644 --- a/app/settings.py +++ b/app/settings.py @@ -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,"降档回差")): 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","page_seconds",10,"轮播周期","Page interval",minimum=1,maximum=120,unit="s") option("rgb","alert_mode","breathe","告警模式","Alert mode","enum",choices=["breathe","flash"]) diff --git a/config/pigway-cooling-hat.conf b/config/pigway-cooling-hat.conf index d053f7e..90eaddc 100644 --- a/config/pigway-cooling-hat.conf +++ b/config/pigway-cooling-hat.conf @@ -109,3 +109,8 @@ memory_breathe_color = purple storage_breathe_color = white network_breathe_color = blue service_breathe_color = cyan + +[integration] +# 默认不开启接入 API;true 允许本机监控通过 Unix socket 接入。修改后重启插件。 +# Local features work independently. Restart after changing this setting. +enabled = false diff --git a/docs/HARDWARE_PLUGIN_API.md b/docs/HARDWARE_PLUGIN_API.md index f8b1301..e6c7eb3 100644 --- a/docs/HARDWARE_PLUGIN_API.md +++ b/docs/HARDWARE_PLUGIN_API.md @@ -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 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. + +## 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. diff --git a/install.sh b/install.sh index a23cf6c..8c33289 100755 --- a/install.sh +++ b/install.sh @@ -3,11 +3,14 @@ set -euo pipefail cd "$(dirname "$0")" NO_START=0 IMPORT='' +INTEGRATION='' while [ "$#" -gt 0 ]; do case "$1" in + --enable-integration) INTEGRATION=true; shift ;; + --disable-integration) INTEGRATION=false; shift ;; --no-start) NO_START=1; shift ;; --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 ;; esac 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; } # Validate a possible migration before changing installed files. 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 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/ @@ -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)" PYTHONDONTWRITEBYTECODE=1 python3 app/migrate.py "$IMPORT" --output /etc/pigway-cooling-hat.conf 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/ systemctl daemon-reload if [ "$NO_START" -eq 0 ]; then