Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5e668d545 | ||
|
|
c947cbe934 | ||
|
|
93d5f0cdd7 | ||
|
|
d6df234154 |
@@ -1,3 +1,5 @@
|
||||
# 树莓派接入插件 v1.2.2
|
||||
|
||||
2026-10-14 网络卡片改为按 network.link 显示已连接/已断开,字段缺失显示状态未确认,与 NAS 的接口连接语义一致。页面不再把 network.internet 用作接口状态文案。Agent 的既有互联网探测和告警逻辑不修改。两项后端测试及全页三尺寸中英文主题浏览器验证通过。部署使用停服一致性数据备份 /volume1/homes/way/pigway-console-backup-synology-vpi-link-v1.2.2-20261014/data.tar.gz,目录 0700;旧树莓派插件保存在数据卷 adapter-backups/vpi-link-v1.2.2/pigway-agent。
|
||||
|
||||
生产安装并启用 1.2.2,实际 UI 已使用 network.link 且不存在“互联网正常”文案;认证 API 两设备在线,实时流量和历史指标保留。验证会话删除。Tea ZIP 与 SHA256 附件下载校验一致。
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# pigway-console-adapter-raspberry-pi v1.3.0
|
||||
|
||||
2026-10-15 已与 Console v2.4.0 部署。声明每秒缓存读取并透传 Agent 的 system/network 真实采样时间。树莓派网络仍每 5 秒采样,历史去重验证通过。
|
||||
|
||||
生产两设备在线。认证 API 连续观测确认树莓派缓存每秒、网络 5 秒、NAS 实时约 10 秒;最近三个网络 10 秒历史桶各 2 个样本,没有每秒重复入库。Console 保留原名称、证书、存储缓存和一年历史策略。部署详情见 Console [生产记录](https://tea.pigway.com/way/pigway-device-console/src/branch/main/docs/DEPLOYMENT-v2.4.0-2026-10-15.md)。
|
||||
@@ -32,3 +32,5 @@ v1.1.0 已与 Console v2.1.0 部署,真实树莓派在线并开始记录历史
|
||||
当前网络卡片以实时上下行速率为主文字,详见 [v1.2.1 部署记录](DEPLOYMENT-v1.2.1-2026-10-14.md)。
|
||||
|
||||
网络连接文案统一为接口链路状态,见 [v1.2.2](DEPLOYMENT-v1.2.2-2026-10-14.md)。
|
||||
|
||||
生产 v1.3.0 与按设备周期采集的核验见 [部署记录](DEPLOYMENT-v1.3.0-2026-10-15.md)。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "pigway-agent",
|
||||
"name": "PIGWay Raspberry Pi Agent",
|
||||
"version": "1.2.2",
|
||||
"version": "1.3.1",
|
||||
"protocol_version": 1,
|
||||
"entrypoint": "worker.py",
|
||||
"runtime": "python3",
|
||||
@@ -38,5 +38,8 @@
|
||||
"contract": "console-ui-v1",
|
||||
"entrypoint": "ui/index.js"
|
||||
},
|
||||
"legacy_connection_fields": true
|
||||
"legacy_connection_fields": true,
|
||||
"collection": {
|
||||
"interval_seconds": 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,14 @@ def request(device, endpoint, method='GET', payload=None, query=None):
|
||||
raise AdapterError('RESULT_UNKNOWN' if method == 'PUT' else 'DEVICE_UNAVAILABLE') from None
|
||||
|
||||
|
||||
def collection_interval(value):
|
||||
declaration = value.get('collection', {})
|
||||
interval = declaration.get('interval_seconds') if isinstance(declaration, dict) else None
|
||||
if not isinstance(interval, (int, float)) or isinstance(interval, bool) or not math.isfinite(interval) or not 1 <= interval <= 3600:
|
||||
interval = MANIFEST['collection']['interval_seconds']
|
||||
return max(MANIFEST['collection']['interval_seconds'], interval)
|
||||
|
||||
|
||||
def collect(device):
|
||||
value = request(device, '/status')
|
||||
if not isinstance(value.get('system'), dict) or not isinstance(value.get('machine'), dict):
|
||||
@@ -80,9 +88,15 @@ def collect(device):
|
||||
rate = value.get('network', {}).get(field)
|
||||
if isinstance(rate, (int, float)) and not isinstance(rate, bool) and math.isfinite(rate) and rate >= 0:
|
||||
metrics.append({'resource_id': 'host', 'key': key, 'value': rate, 'unit': 'bytes_per_second'})
|
||||
for metric in metrics:
|
||||
source = value.get('network', {}) if metric['key'].startswith('network_') else value.get('system', {})
|
||||
at = source.get('observed_at') if metric['key'] != 'uptime_seconds' else value.get('timestamp')
|
||||
if isinstance(at, (int, float)) and not isinstance(at, bool) and math.isfinite(at) and at > 0:
|
||||
metric['observed_at'] = at
|
||||
return {'schema_version': 1, 'observed_at': value.get('timestamp'),
|
||||
'identity': {'reported_name': value['machine'].get('name'), 'category': 'host',
|
||||
'details': value.get('device', {})},
|
||||
'collection': {'interval_seconds': collection_interval(value)},
|
||||
'capabilities': MANIFEST['capabilities'], 'metrics': metrics,
|
||||
'resources': [{'id': 'host', 'type': 'host'}],
|
||||
'alerts': value.get('display', {}).get('alerts', []),
|
||||
|
||||
@@ -11,7 +11,7 @@ class MetricsTests(unittest.TestCase):
|
||||
value = {'timestamp': 123, 'machine': {'name':'Pi'}, 'system': {
|
||||
'cpu_percent': 10, 'memory_total_bytes': 8000, 'memory_used_bytes': 4000,
|
||||
'disk_total_bytes': 16000, 'disk_used_bytes': 5000},
|
||||
'uptime_seconds': 1000, 'network': {'link_speed_mbps':1000,'rx_bytes_per_second':0,'tx_bytes_per_second':1234},
|
||||
'uptime_seconds': 1000, 'network': {'observed_at':120,'link_speed_mbps':1000,'rx_bytes_per_second':0,'tx_bytes_per_second':1234},
|
||||
'display': {'alerts': []}}
|
||||
with patch.object(worker,'request',return_value=value): result=worker.collect({})
|
||||
values = {m['key']:m['value'] for m in result['metrics']}
|
||||
@@ -20,8 +20,15 @@ class MetricsTests(unittest.TestCase):
|
||||
self.assertEqual(values['network_link_speed_mbps'],1000)
|
||||
self.assertEqual(values['network_rx_bytes_per_second'],0)
|
||||
self.assertEqual(values['network_tx_bytes_per_second'],1234)
|
||||
self.assertEqual(next(m['observed_at'] for m in result['metrics'] if m['key']=='network_tx_bytes_per_second'),120)
|
||||
self.assertEqual(result['collection']['interval_seconds'],2)
|
||||
self.assertTrue(result['alerts_complete'])
|
||||
|
||||
def test_collection_minimum_and_slower_device(self):
|
||||
self.assertEqual(worker.collection_interval({'collection':{'interval_seconds':1}}),2)
|
||||
self.assertEqual(worker.collection_interval({'collection':{'interval_seconds':5}}),5)
|
||||
self.assertEqual(worker.collection_interval({'collection':{'interval_seconds':True}}),2)
|
||||
|
||||
def test_absent_or_boolean_fields_not_invented(self):
|
||||
value={'system': {'cpu_percent': True}, 'machine':{}, 'network': {'link_speed_mbps': None, 'rx_bytes_per_second':float('nan'), 'tx_bytes_per_second':True}}
|
||||
with patch.object(worker,'request',return_value=value): result=worker.collect({})
|
||||
|
||||
Reference in New Issue
Block a user