# Research: ASUS RT-AX86U Pro Router Monitoring for Annie

**Created:** 2026-04-01 (Session 382)
**Updated:** 2026-04-01 (Session 383 — Phase 1 complete, `asusrouter` library validated)
**Status:** Phase 1 COMPLETE. Phase 2 in progress.

---

## 1. Router Hardware & Firmware

### Model: ASUS RT-AX86U Pro
- **SoC:** Quad-core 2GHz Broadcom BCM4912
- **RAM:** 1 GB
- **Flash:** 256 MB
- **WiFi:** WiFi 6 AX5700 (861 Mbps 2.4GHz + 4804 Mbps 5GHz)
- **Ports:** 2.5G WAN, 4x Gigabit LAN, USB 3.0
- **IP:** 192.168.50.1 (confirmed Session 383 — NOT 192.168.68.1 which is TP-Link Deco)
- **SSID:** RoshanReethi
- **Admin:** admin / (stored in .env on Titan, never in code)

### Firmware Decision: Stock AsusWRT Only

**OpenWrt:** REJECTED — Broadcom AX chipset not supported by OpenWrt or DD-WRT. Forum threads confirm no firmware available for RT-AX86U Pro.

**AsusWRT-Merlin:** REJECTED by Rajesh — concerns about:
- Breaking ASUS mobile app cloud features
- AiProtection/Traffic Analyzer may behave differently
- Unnecessary risk when stock firmware solves the core problem
- Merlin users report running 388.8_4 with Entware, SSH, vnstat, node-exporter — all viable but not needed

**Stock firmware provides:**
- **Traffic Analyzer** — per-device bandwidth with app-level classification (streaming, web, download, gaming). Requires agreeing to Trend Micro data collection.
- **Traffic Monitor** — real-time graphs for WAN, LAN, each WiFi band (upload/download). Daily and monthly totals.
- **Adaptive QoS** — real-time device bandwidth consumption, prioritization/throttling.
- **Client List** — every connected device with name, IP, MAC, connection type.
- **AiProtection** — Trend Micro security layer, malicious site blocking.

**Stock firmware limitation:** No long-term historical data in exportable format. This is the core gap Annie fills.

### ISP: ACT Fibernet
- **Monthly cap:** 3,300 GB before throttling
- **Connection:** Fiber ethernet into router's 2.5G WAN port
- **Replaces:** ACT's own router

### Network Topology
| Device | IP | Role |
|--------|-----|------|
| Router (AX86U Pro) | **192.168.50.1** | Primary WAN gateway (upstream of Deco) |
| TP-Link Deco (Robotics) | 192.168.68.1 | Mesh AP, RJ45 from ASUS LAN. Gateway for all devices |
| Titan (DGX Spark) | 192.168.68.52 | Main brain, runs Annie |
| Beast (DGX Spark) | 192.168.68.58 | Background agents |
| Panda (RTX 5070 Ti) | 192.168.68.57 | Phone hub, USB to Pixel 9a |
| Pixel 9a | (via USB to Panda) | Annie's phone, Airtel SIM |

---

## 2. Python Library: `asusrouter` v1.21.3

### Overview
- **GitHub:** https://github.com/Vaskivskyi/asusrouter
- **PyPI:** https://pypi.org/project/asusrouter/
- **Docs:** https://asusrouter.vaskivskyi.com/library/
- **Dependencies:** aiohttp >=3.13.3, urllib3, xmltodict
- **Python:** >=3.11
- **Firmware support:** Stock AsusWRT (380.x-388.x) + Merlin
- **Used by:** Home Assistant (battle-tested across thousands of routers)

### Core API

```python
from asusrouter import AsusRouter, AsusData, AsusRouterError
import aiohttp

# Connect (no context manager — manual lifecycle)
session = aiohttp.ClientSession()
router = AsusRouter(
    hostname="192.168.68.1",
    username="admin",
    password="<from .env>",
    use_ssl=False,
    session=session,
)
await router.async_connect()

# Get data
clients = await router.async_get_data(AsusData.CLIENTS)      # Connected devices
cpu = await router.async_get_data(AsusData.CPU)               # CPU usage
ram = await router.async_get_data(AsusData.RAM)               # RAM usage
temp = await router.async_get_data(AsusData.TEMPERATURE)      # Temperature
wan = await router.async_get_data(AsusData.WAN)               # WAN status + IP
network = await router.async_get_data(AsusData.NETWORK)       # Total bandwidth
firmware = await router.async_get_data(AsusData.FIRMWARE)      # Firmware version
identity = await router.async_get_identity()                   # Router model info

# Cleanup
await router.async_disconnect()
await session.close()
```

### Connected Devices (AsusData.CLIENTS)

Returns `dict[mac_address, AsusClient]`:
```python
# AsusClient fields:
client.state                          # ConnectionState.CONNECTED / DISCONNECTED
client.description.name               # Hostname
client.description.mac                # MAC address
client.description.vendor             # Device vendor
client.connection.type                # ConnectionType.WIRED / WLAN_2G / WLAN_5G
client.connection.ip_address          # IPv4 address
client.connection.online              # bool
client.connection.internet_state      # bool - has internet access

# WiFi-specific (AsusClientConnectionWlan):
client.connection.rssi                # Signal strength (negative dBm)
client.connection.rx_speed            # RX rate Mbps
client.connection.tx_speed            # TX rate Mbps
client.connection.since               # Connected since (datetime)
client.connection.guest               # On guest network?
```

### System Metrics

```python
# CPU: {'cpu1_total': float, 'cpu1_usage': float, ...}
# RAM: {'total': int (KB), 'free': int, 'used': int, 'usage': float %}
# Temperature: {'cpu': float (celsius)}
# WAN: {'ip_address': str, 'gateway': str, 'dns': list, 'uptime': int}
```

### Known Limitations
1. **NO per-device bandwidth** — only total network throughput via AsusData.NETWORK
2. **No async context manager** — manual connect/disconnect
3. **Firmware 388.10 crashes httpd** — use >=388.10_2 or <=388.9_2
4. **Cache default 5s** — configurable, force refresh with `force=True`
5. **Two HTTP libs** — asusrouter uses aiohttp, Annie uses httpx. Accept coexistence.
6. **AsusData.SYSINFO** — Merlin firmware only, won't work on stock

### Error Classes
```python
AsusRouterError              # Base
AsusRouterConnectionError    # Network issues
AsusRouterTimeoutError       # Timeout
AsusRouterAccessError        # Auth failed
AsusRouter404Error           # Endpoint not found
```

### Router Identity Object
```python
identity = await router.async_get_identity()
identity.model    # "RT-AX86U Pro"
identity.firmware # Firmware version
identity.merlin   # bool — is Merlin?
identity.ookla    # bool — built-in speedtest?
identity.led      # bool — LED control?
identity.aura     # bool — RGB control?
```

---

## 3. ISP Accountability — Speed Testing

### Approach: Independent speed tests from Titan

The ASUS router has no built-in ISP verification. We run `speedtest-cli` from Titan every 2 hours to build an independent speed log.

```bash
# Install
pip install speedtest-cli

# Usage
speedtest --simple
# Download: 287.43 Mbit/s
# Upload: 142.08 Mbit/s
# Ping: 3.21 ms
```

### What this enables
- Track actual vs advertised speed (ACT promises 600 Mbps)
- Detect throttling patterns (evening dips, post-cap throttling)
- Uptime tracking via WAN status snapshots (every 5 min)
- Monthly report: avg speed, uptime %, outage count, usage vs 3,300 GB cap

### Speed test considerations
- **Frequency:** Every 2 hours (12/day) — enough to detect patterns without saturating bandwidth
- **Timeout:** 30s subprocess timeout (speedtest-cli can hang)
- **Data usage:** Each test ~40-80 MB. At 12/day = ~0.5-1 GB/day = ~15-30 GB/month. Acceptable.
- **Server selection:** speedtest-cli auto-selects nearest server (likely ACT Fibernet - Bengaluru)

---

## 4. SMS Alerts via Pixel 9a (WAN Down Fallback)

### Problem
When internet (WAN) goes down, Telegram (cloud-based) can't deliver alerts. But the LAN still works.

### Solution: Pixel 9a + Tasker + ADB

**Path:** Titan (detects WAN down) → SSH to Panda (LAN) → ADB to Pixel 9a (USB) → Tasker → SMS to Rajesh

### ADB SMS Methods Evaluated

| Method | Works? | Notes |
|--------|--------|-------|
| `adb shell service call isms` | NO | Android security hardening blocks this |
| `adb shell am start SENDTO` | PARTIAL | Opens SMS compose but needs manual tap |
| **Tasker + broadcast intent** | **YES** | Reliable, well-documented, used in her-os ADB stack |
| Custom SMS APK | YES | Fallback option, more work |

### Tasker Broadcast Command (copy-ready)
```bash
adb shell am broadcast \
  -a net.dinglisch.android.taskerm.ACTION_TASK \
  -e task_name "send_sms_to_rajesh" \
  -e phone_number "+91XXXXXXXXXX" \
  -e message "ALERT: Internet down. Local network OK."
```

### Setup Checklist
- [ ] Install Tasker from Play Store (~₹170)
- [ ] Grant SMS + Accessibility permissions to Tasker
- [ ] Create task `send_sms_to_rajesh`: Net → Send SMS, params from intent extras
- [ ] Add Rajesh's phone number to .env as RAJESH_PHONE
- [ ] Test: `adb shell am broadcast ...` → verify SMS received
- [ ] Disable phantom process killer: `adb shell settings put global settings_enable_monitor_phantom_procs false`

### Reliability
- Tasker foreground service survives Doze mode and battery optimization
- Pixel 9a auto-reconnects ADB on trusted WiFi networks
- SMS rate limit: Airtel allows ~1/30s — WAN alerts at 5-min intervals are safe
- Cost: Tasker ₹170 one-time + Airtel SIM ₹199/month

---

## 5. Alert Architecture

### Alert Types

| Alert | Trigger | Cooldown | Channel |
|-------|---------|----------|---------|
| Unknown device | MAC not in device registry | Once per MAC | Telegram |
| WAN down | WAN status != connected >2 min | 30 min | **SMS** + Telegram when restored |
| WAN restored | WAN back after outage | — | Telegram (with downtime duration) |
| Bandwidth spike | Device daily RX > 3x 7-day avg | 24h/device | Telegram |
| Speed degradation | Speed test < 50% of 7-day avg | 6h | Telegram |
| Monthly cap warning | Projected > 90% of 3,300 GB | 24h | Telegram |
| Device missing | Known device offline >48h | 7 days | Telegram (informational) |

### Routing Logic
```
WAN down → SMS (Telegram won't work)
WAN restored → Telegram (internet is back, richer format)
Everything else → Telegram (internet available)
```

### Delivery Mechanism
- **Telegram alerts:** Write JSON to `~/.her-os/annie/task_results/` — Telegram bot polls this directory (existing pattern from task_delivery.py)
- **SMS alerts:** SSH to Panda → ADB broadcast to Pixel 9a → Tasker sends SMS

---

## 6. Device Identity Registry

### Design
File: `~/.her-os/annie/router/devices.json`

```json
{
  "AA:BB:CC:DD:EE:FF": {
    "name": "Titan (DGX Spark)",
    "type": "server",
    "known": true,
    "first_seen": "2026-04-01T10:00:00+05:30"
  },
  "11:22:33:44:55:66": {
    "name": "Unknown",
    "type": "unknown",
    "known": false,
    "first_seen": "2026-04-02T15:30:00+05:30",
    "alerted": true
  }
}
```

### Auto-learning
- On first run: all devices from router marked as "unknown"
- Rajesh names them via Telegram (Annie asks: "New device `Samsung-Galaxy` at .68.44 — who is this?")
- Router hostname used as initial hint
- Match on both MAC and hostname (MAC randomization on iPhones changes MAC periodically)

---

## 7. Data Storage

### Directory: `~/.her-os/annie/router/`

| File | Format | Size Estimate |
|------|--------|--------------|
| `snapshots/YYYY-MM-DD.jsonl` | JSONL (one per day) | ~576 KB/day (288 snapshots × 2 KB) |
| `speedtests.jsonl` | JSONL (single) | ~1 KB/day (12 tests × 80 bytes) |
| `alerts.jsonl` | JSONL (single) | Varies |
| `devices.json` | JSON (mutable) | <10 KB |
| `daily/YYYY-MM-DD.json` | JSON (one per day) | ~2 KB/day |

**90-day retention:** ~52 MB snapshots + ~90 KB speedtests + ~180 KB dailies = ~53 MB total. Negligible.

---

## 8. Prior Discussion History (Sessions 370-375, March 24-25, 2026)

### Router Selection Discussion
- Rajesh was investigating bandwidth issues on ACT Fibernet connection
- Considered RT-AX86U Pro as primary router upgrade
- Key requirement: per-device bandwidth visibility to find the "data hog"

### Firmware Discussion
- **Rajesh's concern:** "I am afraid of flashing the router, can't we work with what it comes with?"
- **Resolution:** Stock firmware + Titan-side scraper. "The stock firmware already solves your problem."
- **Merlin was considered** for vnstat, node-exporter, Prometheus/Grafana — but stock + Annie achieves the same goal without firmware risk.

### Agreed Architecture (from that session)
> "I can write a Python script that runs as a cron job on Titan, logs in to the router over WiFi, pulls the per-device traffic stats periodically, and stores them in a SQLite database. Over time this becomes your audit trail."

This plan implements exactly that vision, using JSONL instead of SQLite (consistent with her-os patterns), and adding ISP verification + SMS alerts.

---

## 9. Adversarial Review Findings (35 issues: 7 CRITICAL, 11 HIGH, 9 MEDIUM, 8 LOW)

Full review: `~/.claude/plans/flickering-roaming-cookie-agent-ae45f09bf6afedef1.md`

### CRITICAL Issues

| # | Issue | Fix |
|---|-------|-----|
| C1 | **`AsusData.NETWORK` is PING data, not traffic** — plan assumed it was bandwidth | Use `CLIENTS` curRx/curTx fields (instantaneous KiB/s). Integrate over 5-min intervals. |
| C2 | **Per-device cumulative bandwidth NOT in API** — Traffic Analyzer data is in JFFS, no HTTP endpoint | Phase 1: discover CGI endpoints. Fallback: curRx/curTx × 300s approximation. |
| C3 | **Shell injection in SMS command** — f-string with device names into `adb shell` | Use `subprocess_exec` with arg list. `shlex.quote()` all values. |
| C4 | **No reconnection after router reboot** — session dies permanently | try/except + disconnect/reconnect + exponential backoff (5s/15s/60s/300s). |
| C5 | **speedtest-cli hangs** — blocks entire collector loop | `asyncio.wait_for(timeout=90)`. Separate asyncio task for speed tests. |
| C6 | **MAC randomization** — iOS 14+/Android 10+ randomize MACs by default | Disable on personal devices. Add "learn MAC" command. Suppress guest-band alerts. |
| C7 | **No test fixtures** — can't test without real router | Capture real CGI responses in Phase 1 as JSON fixtures. Mock at `collect_snapshot()` level. |

### HIGH Issues

| # | Issue | Fix |
|---|-------|-----|
| H1 | AsusData.SPEEDTEST may not exist on stock | Use external Ookla CLI only |
| H2 | `speedtest-cli` PyPI package abandoned since 2021 | Use official Ookla `speedtest` binary (`apt install`) |
| H3 | Router password in cleartext over HTTP | Enable HTTPS on router, `use_ssl=True` |
| H4 | WAN IP logged in speedtests.jsonl | Strip or hash before writing |
| H5 | speedtests.jsonl + alerts.jsonl grow unbounded | Rotate to monthly files |
| H6 | Panda offline → SMS fails | 5s SSH timeout, log failure, consider Titan speaker alarm |
| H7 | 90-day query loads 130 MB into RAM | Use daily rollups for >30 day queries |
| H8 | aiohttp + httpx dual stack | Isolate aiohttp to router_monitor.py only |
| H9 | text_llm.py already 1579 lines | Gate with env var, import schema from router_monitor |
| H10 | First run = 13 unknown device alerts | Silent discovery period + interactive naming |
| H11 | Guest network devices trigger false alerts | Auto-classify `guest=True` devices |
| H12 | collector.log grows unbounded | Use loguru rotation |

### MEDIUM Issues
- Let AsusRouter manage own session (don't pass external one)
- Constructor param is `hostname=` not `host=`
- Hardcode phone number in Tasker task (not from intent extra)
- Pre-check `adb devices` before SMS
- Daemon heartbeat file for health monitoring
- Peak-hour speed tests (30 min during 7-11 PM IST)
- Add `_TOOL_INTENT` regex for Nemotron routing
- Validate RAJESH_PHONE format at startup
- Weekly cron: `"30 4 * * 0"` for 10 AM IST (not `"0 4"` which is 9:30 AM)
- Add `"_v": 1` schema version to JSONL records
- Retry with backoff at startup (router may boot after Titan)
- Update requirements.txt

---

## 10. Phase 1 Browser Exploration Results (Session 383)

### Router Confirmed
- **Model**: RT-AX86U_PRO (confirmed via `nvram_get(productid)`)
- **Firmware**: 3.0.0.6 build 102 (SAFE — not the problematic 388.10)
- **IP**: 192.168.50.1 (NOT 192.168.68.1 which is TP-Link Deco)
- **CPU temp**: 76.8°C
- **Uptime at test**: ~1.7h (6212s since boot)
- **Ookla speedtest**: `nvram_get(ookla_speedtest_state)` was empty, BUT library detected `ookla=True` in services. Built-in Ookla IS available.

### Network Topology (confirmed)
```
ISP (ACT Fibernet) → ASUS RT-AX86U Pro (192.168.50.x, RoshanReethi)
  → TP-Link Deco (192.168.50.160, "decoMeshX10", RJ45 from ASUS LAN)
    → 192.168.68.x subnet: Titan(.52), Beast(.58), Panda(.57), laptop(.56)
```
- **Double-NAT**: Deco's WAN on ASUS 192.168.50.x, creates 192.168.68.x
- **Only 5 devices visible on ASUS**: R15, Logitech, decoMeshX10, D-Link, 1 offline WiFi
- **Titan/Beast/Panda NOT individually visible** — all behind Deco as single device

### Authentication (CRITICAL for `asusrouter` library)
- **Nonce-based challenge-response auth** (NOT simple base64 as older firmware):
  1. POST JSON `{"id": "<random>"}` to `/get_Nonce.cgi` → get nonce
  2. Compute `sha256(username:nonce:password:cnonce)` → login_authorization
  3. POST form to `/login.cgi` with login_authorization, id, cnonce
- **Session maintained via cookies** after login
- **`asusrouter` v1.21.3 MUST support this** — check before using. If not, implement custom auth.

### CGI API Endpoints (validated)

| Endpoint | Method | Body | Response | Notes |
|----------|--------|------|----------|-------|
| `/appGet.cgi` | POST | `hook=get_clientlist()` | JSON, 4.6KB | Per-device: MAC, IP, name, vendor, isOnline, isWL, rssi, curTx/curRx |
| `/appGet.cgi` | POST | `hook=netdev(appobj)` | JSON, 225B | Hex byte counters: BRIDGE, INTERNET, WIRED, WIRELESS0, WIRELESS1 |
| `/appGet.cgi` | POST | `hook=netdev(details)` | JSON, 277B | Same counters in flat key-value format |
| `/appGet.cgi` | POST | `hook=nvram_get(X)` | JSON | Per-variable: firmver, buildno, productid, wan0_state_t, etc. |
| `/appGet.cgi` | POST | `hook=get_cpu_temperature()` | JSON | `{"get_cpu_temperature": 76.848}` |
| `/appGet.cgi` | POST | `hook=uptime()` | JSON | Uptime string with seconds since boot |
| `/TrafficAnalyzer_Statistic.asp` | GET | — | HTML+JS, 52KB | **Per-device bandwidth** — server-rendered JavaScript variables |

### Per-Device Bandwidth (Critical C2 Resolution)
- **`curTx`/`curRx` in clientlist = WiFi link speed (Mbps), NOT actual throughput**
  - Wireless devices: populated (e.g., Logitech: curTx=65, curRx=72.2 Mbps)
  - Wired devices: empty
- **`totalTx`/`totalRx` = ALL EMPTY** — no cumulative per-device data in clientlist API
- **Traffic Analyzer is the source**: `/TrafficAnalyzer_Statistic.asp` (52KB) contains:
  - `top5_client_array` — top 5 bandwidth consumers
  - `total_clients_array` — all clients with traffic
  - `total_apps_array` — app-level classification
  - `router_traffic_array` — router traffic
  - Data is server-side rendered (no separate JSON API endpoint)
- **Implementation**: HTTP GET the ASP page → regex/parse JavaScript variable assignments
- **TM EULA accepted (Session 383)**: `bwdpi_db_enable=1`, `TM_EULA=1`. Data now recording hourly.
- **`all_client_traffic` format**: `MAC,rx_bytes,tx_bytes,MAC,rx_bytes,tx_bytes,...` (CSV, easy to parse)
- **`router_traffic_array`**: 48 comma-separated values (24h × rx/tx pairs), hourly totals
- **Visibility limitation**: Titan/Beast/Panda behind Deco appear as single device. To see individual servers, rewire to ASUS LAN ports directly.

### Network Traffic Counters (netdev)
Cumulative byte counters (32-bit hex, wrapping):
- `BRIDGE`: total bridge traffic
- `INTERNET`: WAN traffic
- `WIRED`: all wired port traffic
- `WIRELESS0`: 2.4GHz band
- `WIRELESS1`: 5GHz band
- **Bandwidth calculation**: sample twice, compute `(diff_bytes * 8) / interval_seconds` = bits/sec

### Implications for the Plan
1. **ROUTER_HOST must be 192.168.50.1** (not .68.1) in .env
2. **Auth**: Verify `asusrouter` supports nonce-based auth. Fallback: custom auth module.
3. **Per-device bandwidth**: Parse `/TrafficAnalyzer_Statistic.asp` — NOT available via simple CGI hook
4. **Deco hides devices**: Titan/Beast/Panda appear as single "decoMeshX10" device. Per-device visibility is limited to devices directly on ASUS WiFi.
5. **No built-in Ookla speedtest**: Must use external speedtest-cli from Titan (H1 confirmed)
6. **5 devices, not 13**: The initial H10 alert storm concern (13 unknowns) is reduced — only 5 devices on ASUS

---

## 11. `asusrouter` v1.21.3 Library Validation (Session 383)

### Authentication: WORKS
- Library handles nonce-based SHA256 auth **transparently** — no custom code needed
- `AsusRouter(hostname, username, password, use_ssl=False, session=session)` → `async_connect()` succeeds
- The library internally handles `get_Nonce.cgi` → SHA256 → `login.cgi` flow

### Router Identity (from `async_get_identity()`)
```python
AsusDevice(
    serial='T5IG5Q200070HEZ',
    mac='60:CF:84:5B:A8:D8',
    product_id='RT-AX86U_PRO',
    model='RT-AX86U Pro',
    brand='ASUSTek',
    firmware=3.0.0.6.102.37421_gd6a5982_494-gbf786,
    merlin=False,
    aimesh=True,
    ookla=True,        # Built-in Ookla speedtest IS available!
    led=True,
    aura=False,
    wlan=[FREQ_2G, FREQ_5G],
    services=['bwdpi', 'ookla', 'amas', 'smart_connect', 'wpa3', ...],
)
```

### Available Endpoints (auto-detected by library)
| Endpoint | CGI Path | Purpose |
|----------|----------|---------|
| HOOK | `appGet.cgi` | Main data query API |
| TEMPERATURE | `ajax_coretmp.asp` | CPU/WiFi temperature |
| DEVICEMAP | `ajax_status.xml` | Device status overview |
| STATE | `state.js` | Router state variables |
| ETHERNET_PORTS | `ajax_ethernet_ports.asp` | Port status/speeds |
| PORT_STATUS | `get_port_status.cgi` | Detailed port info |
| UPDATE_CLIENTS | `update_clients.asp` | Refresh client data |
| FIRMWARE | `detect_firmware.asp` | Firmware update check |
| **TRAFFIC_ETHERNET** | `get_diag_eth_traffic.cgi` | **Per-ethernet-port traffic** |
| **TRAFFIC_WIFI** | `get_diag_wifi_traffic.cgi` | **Per-WiFi-client traffic** |
| **TRAFFIC_BACKHAUL** | `get_diag_sta_traffic.cgi` | **Per-station backhaul traffic** |
| ONBOARDING | `ajax_onboarding.asp` | AiMesh onboarding |
| NETWORK | `netool.cgi` | Network tools (ping, etc.) |

### AsusData Types — Validated Responses

**CPU** (`AsusData.CPU`):
```python
{
    'total': {'total': 2607385.0, 'used': 41685.0, 'usage': None},
    1: {'total': 650880, 'used': 11856, 'usage': None},
    2: {'total': 651395, 'used': 12455, 'usage': None},
    3: {'total': 652807, 'used': 7820, 'usage': None},
    4: {'total': 652303, 'used': 9554, 'usage': None}
}
# 4 cores. Usage = used/total * 100. 'usage' field populated on second query.
```

**RAM** (`AsusData.RAM`):
```python
{'free': 567968, 'total': 1048576, 'used': 480608, 'usage': 45.83}
# 1 GB total, 45.8% used. Values in KB.
```

**Temperature** (`AsusData.TEMPERATURE`):
```python
{'cpu': 77.398}
# CPU temperature in Celsius.
```

**WAN** (`AsusData.WAN`):
```python
{
    'internet': {'unit': 0, 'link': ConnectionStatus.CONNECTED, 'ip_address': '<WAN_IP>'},
    0: {
        'auxstate': ConnectionStatus.DISCONNECTED,
        'primary': True,
        'protocol': IPAddressType.DHCP,
        'real_ip': '<PUBLIC_IP>',
        'state': ConnectionStatus.CONNECTED,
    }
}
# WAN state 2 = CONNECTED. Protocol = DHCP from ISP (ACT Fibernet).
# IMPORTANT: real_ip is public IP — strip or hash before logging (H4).
```

**Network** (`AsusData.NETWORK`):
```python
{
    'wan': {'rx': 3035359875, 'tx': 3103330900, 'rx_speed': 0.0, 'tx_speed': 0.0},
    'wired': {'rx': 3101190800, 'tx': 3034837274, 'rx_speed': 0.0, 'tx_speed': 0.0},
    'bridge': {'rx': 3058818119, 'tx': 3041398614, 'rx_speed': 0.0, 'tx_speed': 0.0},
    '2ghz': {'rx': 3553223, 'tx': 13405204, 'rx_speed': 0.0, 'tx_speed': 0.0},
    '5ghz': {'rx': 7044, 'tx': 1974578, 'rx_speed': 0.0, 'tx_speed': 0.0}
}
# Cumulative bytes (32-bit wrapping). Speed needs 2+ samples.
# Note: ASUS calls AsusData.NETWORK but it's aggregate, not per-device.
```

**Firmware** (`AsusData.FIRMWARE`):
```python
{
    'current': '3.0.0.6.102.37421',
    'state': False,      # No critical update pending
    'available': '3.0.0.6.102.34406',  # Older available (not newer)
}
```

**Ports** (`AsusData.PORTS`):
```python
{
    PortType.WAN: {0: {'state': True, 'link_rate': PortSpeed.LINK_1000, 'max_rate': PortSpeed.LINK_1000}},
    PortType.LAN: {1: {'state': True, ...}, 2: {...}, ...}
}
# WAN: 1000 Mbps link (Gigabit). LAN ports also available.
```

**Clients** (`AsusData.CLIENTS`):
```python
{
    '44:73:D6:09:E1:DC': AsusClient(
        state=ConnectionState.CONNECTED,
        description=AsusClientDescription(name='Logitech', mac='44:73:D6:09:E1:DC', vendor='Logitech'),
        connection=AsusClientConnectionWlan(
            type=ConnectionType.WLAN_2G,
            ip_address='192.168.50.100',
            ip_method=IPAddressType.DHCP,
            internet_state=True,
            internet_mode=InternetMode.ALLOW,
            online=True,
            guest=False,
            rssi=-52,
            since=datetime(2026, 3, 31, ...),
        )
    ),
    # ... 5 devices total (4 online, 1 offline)
}
```

### Devices on ASUS Network (5 total)
| Name | MAC | IP | Type | Online | Vendor |
|------|-----|-----|------|--------|--------|
| R15 | A8:63:7D:65:DA:09 | .50.153 | Wired | Yes | D-Link (AiMesh node) |
| Logitech | 44:73:D6:09:E1:DC | .50.100 | WiFi 2.4G | Yes | Logitech |
| decoMeshX10 | 3C:64:CF:5E:DE:81 | .50.160 | Wired | Yes | TP-Link Deco |
| D-Link | 78:32:1B:5D:29:78 | .50.122 | Wired | Yes | D-Link |
| (unnamed) | — | .50.126 | WiFi | No | — |

### Key Insights for Implementation
1. **Auth is handled by the library** — no custom code needed. Just use `AsusRouter`.
2. **Ookla IS available** (`ookla=True`) — investigate `AsusData.SPEEDTEST` in Phase 2.
3. **Three traffic diagnostic endpoints exist** — `TRAFFIC_ETHERNET`, `TRAFFIC_WIFI`, `TRAFFIC_BACKHAUL`. These may provide per-device bandwidth. Library may have methods to call them.
4. **bwdpi service is active** — Traffic Analyzer/DPI is enabled on the router.
5. **AiMesh active** — R15 is an AiMesh node (`amesh_isReClient: 1`). Device tracking needs to account for clients connected via mesh nodes.
6. **Speed calculation needs 2+ samples** — `rx_speed`/`tx_speed` are 0.0 on first query. The library computes deltas between consecutive calls.
7. **WAN real_ip must be stripped** from logs (adversarial review H4).

---

## 12. Implementation Plan Reference

Full implementation plan: `/home/rajesh/.claude/plans/flickering-roaming-cookie.md`

Build order:
1. Phase 0: This research doc (done)
2. Phase 1: Browser exploration + library validation (done — Session 383)
3. Phase 2: Core library + collector daemon + tests
4. Phase 3: Annie tool integration (network_status)
5. Phase 4: Scheduled daily/weekly reports
6. Phase 5: start.sh/stop.sh integration
7. Phase 6: SMS alert setup (after Pixel 9a arrives)
