#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ECAT native 主站的采样公共件:HTTP 调用 + 计数器差分。 固件 1.6.15 覆盖窗口布局下,各计数器的口径并不一致,混用会静默算错: * bridge.rx_count / head.global_seq —— u32,**回绕** * bridge.drop —— u16,**回绕**;段 1/段 3 单格"未取先覆"数,恒等于 λ − 实测Hz * rx_batch.lost(主站按 seq 空洞算的单拍丢帧) / pending(从站窗口 OVF) —— **每拍回填清零的单拍值**(ecat_native.c:461),不是累计计数:拿它做差得速率恒为 0, 拿轮询见过的最大值当总丢帧也只是下界 * app/load.rx_torn / pdo_reentry —— u16,**饱和**(到 65535 就停,不是回绕) 饱和量按回绕算会把"已封顶"误报成"发生了 6 万多次",故二者分开处理。 """ import json import urllib.request SATURATE = 65535 # u16 饱和计数的顶 def post(url, path, body=None, timeout=6.0): req = urllib.request.Request( url + path, data=json.dumps(body or {}).encode("utf-8"), headers={"Content-Type": "application/json"}, method="POST") with urllib.request.urlopen(req, timeout=timeout) as r: return json.loads(r.read().decode("utf-8")) def delta_wrap(a, b, bits=16): """回绕计数器的增量。真值下降(重启清零)会表现为一个巨大的正数, 调用方须用 sanity 上界剔除 —— 这里只负责把模数算对。""" m = 1 << bits return (b - a) % m def delta_saturate(a, b): """饱和计数的增量。返回 (增量, 是否已封顶)。 封顶后增量恒为 0 —— 读数 0 既可能是"真没发生"也可能是"早发生了但计不动", 报告里必须写成下界而不是精确值。 """ if b >= SATURATE: return 0, True if b < a: return 0, False # 计数器被回填清零:该窗口作废,不臆造增量 return b - a, False def percentile(values, q): """小样本分位数(最近邻)。不引 numpy,采样窗口只有百来个点。""" if not values: return None s = sorted(values) i = min(len(s) - 1, max(0, int(round(q * (len(s) - 1))))) return s[i]