One-shot deep diagnostics bundle for CSI-node debugging. Runs the exact server-side commands a human would (tcpdump on the CSI UDP port, journalctl for the sensing engine, ss/systemctl, system + wifi state, binary md5s) AND the sensing-server API snapshots, so the Nodes-tab 'Download log
()
| 1289 | return math.sqrt(sum((x - m) ** 2 for x in arr) / n) |
| 1290 | |
| 1291 | |
| 1292 | def _gf_convex_hull(pts): |
| 1293 | """Andrew's monotone chain. pts=[{'x','y','id'}] -> ordered ring.""" |
| 1294 | p = sorted(pts, key=lambda q: (q['x'], q['y'])) |
| 1295 | if len(p) < 3: |
| 1296 | return p |
| 1297 | def cross(o, a, b): |
| 1298 | return (a['x']-o['x'])*(b['y']-o['y']) - (a['y']-o['y'])*(b['x']-o['x']) |
| 1299 | lower = [] |
| 1300 | for q in p: |
| 1301 | while len(lower) >= 2 and cross(lower[-2], lower[-1], q) <= 0: |
| 1302 | lower.pop() |
| 1303 | lower.append(q) |
| 1304 | upper = [] |
| 1305 | for q in reversed(p): |
| 1306 | while len(upper) >= 2 and cross(upper[-2], upper[-1], q) <= 0: |
| 1307 | upper.pop() |
| 1308 | upper.append(q) |
| 1309 | return lower[:-1] + upper[:-1] |
| 1310 | |
| 1311 | |
| 1312 | def _gf_point_in_polygon(pt, poly): |
| 1313 | if not poly or len(poly) < 3: |
| 1314 | return False |
| 1315 | inside = False |
| 1316 | j = len(poly) - 1 |
| 1317 | for i in range(len(poly)): |
| 1318 | xi, yi = poly[i]['x'], poly[i]['y'] |
| 1319 | xj, yj = poly[j]['x'], poly[j]['y'] |
| 1320 | if ((yi > pt['y']) != (yj > pt['y'])) and \ |
| 1321 | (pt['x'] < (xj - xi) * (pt['y'] - yi) / ((yj - yi) or 1e-9) + xi): |
| 1322 | inside = not inside |
| 1323 | j = i |
| 1324 | return inside |
| 1325 | |
| 1326 | |
| 1327 | def _gf_inset(poly, frac): |
| 1328 | if not poly or len(poly) < 3 or not frac: |
| 1329 | return poly |
| 1330 | n = len(poly) |
| 1331 | cx = sum(p['x'] for p in poly) / n |
| 1332 | cy = sum(p['y'] for p in poly) / n |
| 1333 | return [{'x': p['x'] + (cx - p['x']) * frac, 'y': p['y'] + (cy - p['y']) * frac} |
| 1334 | for p in poly] |
| 1335 | |
| 1336 | |
| 1337 | def _rusense_geofence_verdict(positions, windows): |
| 1338 | """Port of evaluateGeofence(). positions={id:{x,y,z}}, windows={id:[rssi…]}. |
| 1339 | Returns a dict incl. ok, energetic, corroborated, interior, inside_perimeter, |
| 1340 | reason, hot_count, total. ok=False when fewer than 3 corners are mapped.""" |
| 1341 | nodes = [] |
| 1342 | for nid, pos in (positions or {}).items(): |
| 1343 | if not isinstance(pos, dict) or not _gf_finite(pos.get('x')) or not _gf_finite(pos.get('y')): |
| 1344 | continue |
| 1345 | win = windows.get(str(nid)) or [] |
| 1346 | disturbance = 1.0 - math.exp(-_gf_std(win) / (_GF_DBM_SCALE or 1e-9)) |
| 1347 | nodes.append({'id': str(nid), 'x': float(pos['x']), 'y': float(pos['y']), |
| 1348 | 'disturbance': disturbance, 'hot': disturbance >= _GF_HOT_THRESHOLD}) |
nothing calls this directly
no test coverage detected