mirror of
https://github.com/ruvnet/RuView
synced 2026-08-07 20:01:43 +00:00
feat(adr-125 tier1+2 iter 2): sensing-server-equivalent for @ruvnet/rvagent
scripts/ruview-sensing-server.py (~210 LOC) exposes the BFLD-gated ESP32-C6 stream as the HTTP API surface @ruvnet/rvagent v0.1.0 (ADR-124, npm) expects. Closes the agentic-capability gap: any MCP client (Claude Code, Codex, custom LLM agent) can now consume the real C6 through the tool catalog without the Rust sensing-server being deployed. Endpoints (mirrors tools/ruview-mcp/src/tools/*.ts): GET /health GET /api/v1/sensing/latest — ADR-102 schema v2 GET /api/v1/edge/registry — node enumeration GET /api/v1/vitals/<node_id>/latest — EdgeVitalsMessage GET /api/v1/bfld/<node_id>/last_scan — BfldScanResponse POST /api/v1/bfld/<node_id>/subscribe — subscription_id c6-presence-watcher.py now writes a companion `/tmp/ruview-last- feature.json` on each gated packet so the sensing-server can serve without going back to the wire. Atomic tmp+rename. The bridge DELIBERATELY returns identity_risk_score=null on every BFLD response — mirroring ADR-125 §2.1.d at the HTTP boundary even though the rvagent schema's slot is nullable. Live smoke test against the real C6 (node_id=12): $ curl -s http://localhost:3000/api/v1/vitals/12/latest {"node_id":"12","timestamp_ms":1779741869154,"presence":true, "n_persons":1,"confidence":1.0,"breathing_rate_bpm":18.75, "heartrate_bpm":40.0,"motion":1.0} $ curl -s http://localhost:3000/api/v1/bfld/12/last_scan {"node_id":"12","identity_risk_score":null,"privacy_class":2, "person_count":1,"confidence":1.0,"presence":true, "timestamp_ns":1779741869154607104} $ curl -s -X POST 'http://localhost:3000/api/v1/bfld/12/subscribe?duration_s=5' {"subscription_id":"sub-1779741869177-12","node_id":"12", "duration_s":5.0,"endpoint_hint":"poll GET ..."} Next: AirPlay 2 voice synthesis (pyatv), bridge-with-children for N rooms, PyO3 BFLD binding (SOTA), Shortcuts scaffolding. Refs ADR-124 (@ruvnet/rvagent contract), ADR-125 §2.1.d, ADR-118. Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -268,6 +268,37 @@ def main() -> int:
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# Companion contract for `scripts/ruview-sensing-server.py` (the
|
||||
# @ruvnet/rvagent compatibility layer): write the full BFLD-gated
|
||||
# feature snapshot so the sensing-server can serve EdgeVitalsMessage
|
||||
# and BfldScanResponse without going back to the wire.
|
||||
feature_path = "/tmp/ruview-last-feature.json"
|
||||
|
||||
def write_feature(gated: dict, motion: bool, occupancy: bool,
|
||||
privacy_cls: int) -> None:
|
||||
try:
|
||||
tmp = feature_path + ".tmp"
|
||||
with open(tmp, "w") as fh:
|
||||
json.dump({
|
||||
"node_id": str(gated["node_id"]),
|
||||
"timestamp_ms": int(time.time() * 1000),
|
||||
"presence": occupancy, # sustained
|
||||
"motion": gated["motion"], # 0..1 float
|
||||
"presence_score": gated["presence"],
|
||||
"n_persons": 1 if occupancy else 0,
|
||||
"confidence": min(1.0, max(0.0, gated["motion"])),
|
||||
"breathing_rate_bpm": (gated["resp_bpm"]
|
||||
if gated.get("resp_bpm") else None),
|
||||
"heartrate_bpm": (gated["hb_bpm"]
|
||||
if gated.get("hb_bpm") else None),
|
||||
"anomaly_score": gated.get("anomaly"),
|
||||
"privacy_class": privacy_cls,
|
||||
"ts": time.time(),
|
||||
}, fh)
|
||||
os.replace(tmp, feature_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
while running:
|
||||
try:
|
||||
buf, _addr = sock.recvfrom(2048)
|
||||
@@ -336,6 +367,8 @@ def main() -> int:
|
||||
if (motion != prev_motion
|
||||
or not state_path.endswith(".disabled")):
|
||||
write_state(motion, occupancy, last_anomaly_ts)
|
||||
write_feature(gated, motion, occupancy,
|
||||
privacy_class)
|
||||
|
||||
# Idle release — if the C6 stops sending entirely, clear motion
|
||||
# AND occupancy.
|
||||
|
||||
Reference in New Issue
Block a user