mirror of
https://github.com/ruvnet/RuView
synced 2026-07-27 18:11:43 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f7d043d727 | |||
| ff91d4e8cf | |||
| fc92436f52 | |||
| 285bb0ad37 | |||
| b5ec4ef043 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"running": true,
|
"running": true,
|
||||||
"startedAt": "2026-02-28T15:54:19.353Z",
|
"startedAt": "2026-03-09T15:26:00.921Z",
|
||||||
"workers": {
|
"workers": {
|
||||||
"map": {
|
"map": {
|
||||||
"runCount": 49,
|
"runCount": 49,
|
||||||
@@ -8,16 +8,16 @@
|
|||||||
"failureCount": 0,
|
"failureCount": 0,
|
||||||
"averageDurationMs": 1.2857142857142858,
|
"averageDurationMs": 1.2857142857142858,
|
||||||
"lastRun": "2026-02-28T16:13:19.194Z",
|
"lastRun": "2026-02-28T16:13:19.194Z",
|
||||||
"nextRun": "2026-02-28T16:28:19.195Z",
|
"nextRun": "2026-03-09T15:56:00.928Z",
|
||||||
"isRunning": false
|
"isRunning": false
|
||||||
},
|
},
|
||||||
"audit": {
|
"audit": {
|
||||||
"runCount": 44,
|
"runCount": 45,
|
||||||
"successCount": 0,
|
"successCount": 0,
|
||||||
"failureCount": 44,
|
"failureCount": 45,
|
||||||
"averageDurationMs": 0,
|
"averageDurationMs": 0,
|
||||||
"lastRun": "2026-02-28T16:20:19.184Z",
|
"lastRun": "2026-03-09T15:43:00.933Z",
|
||||||
"nextRun": "2026-02-28T16:30:19.185Z",
|
"nextRun": "2026-03-09T15:38:00.914Z",
|
||||||
"isRunning": false
|
"isRunning": false
|
||||||
},
|
},
|
||||||
"optimize": {
|
"optimize": {
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"failureCount": 34,
|
"failureCount": 34,
|
||||||
"averageDurationMs": 0,
|
"averageDurationMs": 0,
|
||||||
"lastRun": "2026-02-28T16:23:19.387Z",
|
"lastRun": "2026-02-28T16:23:19.387Z",
|
||||||
"nextRun": "2026-02-28T16:18:19.361Z",
|
"nextRun": "2026-03-09T15:45:00.915Z",
|
||||||
"isRunning": false
|
"isRunning": false
|
||||||
},
|
},
|
||||||
"consolidate": {
|
"consolidate": {
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"failureCount": 0,
|
"failureCount": 0,
|
||||||
"averageDurationMs": 0.6521739130434783,
|
"averageDurationMs": 0.6521739130434783,
|
||||||
"lastRun": "2026-02-28T16:05:19.091Z",
|
"lastRun": "2026-02-28T16:05:19.091Z",
|
||||||
"nextRun": "2026-02-28T16:35:19.054Z",
|
"nextRun": "2026-03-09T16:02:00.918Z",
|
||||||
"isRunning": false
|
"isRunning": false
|
||||||
},
|
},
|
||||||
"testgaps": {
|
"testgaps": {
|
||||||
@@ -44,8 +44,8 @@
|
|||||||
"failureCount": 27,
|
"failureCount": 27,
|
||||||
"averageDurationMs": 0,
|
"averageDurationMs": 0,
|
||||||
"lastRun": "2026-02-28T16:08:19.369Z",
|
"lastRun": "2026-02-28T16:08:19.369Z",
|
||||||
"nextRun": "2026-02-28T16:22:19.355Z",
|
"nextRun": "2026-03-09T15:54:00.920Z",
|
||||||
"isRunning": true
|
"isRunning": false
|
||||||
},
|
},
|
||||||
"predict": {
|
"predict": {
|
||||||
"runCount": 0,
|
"runCount": 0,
|
||||||
@@ -64,8 +64,8 @@
|
|||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"autoStart": false,
|
"autoStart": false,
|
||||||
"logDir": "/home/user/wifi-densepose/.claude-flow/logs",
|
"logDir": "/Users/cohen/GitHub/ruvnet/RuView/.claude-flow/logs",
|
||||||
"stateFile": "/home/user/wifi-densepose/.claude-flow/daemon-state.json",
|
"stateFile": "/Users/cohen/GitHub/ruvnet/RuView/.claude-flow/daemon-state.json",
|
||||||
"maxConcurrent": 2,
|
"maxConcurrent": 2,
|
||||||
"workerTimeoutMs": 300000,
|
"workerTimeoutMs": 300000,
|
||||||
"resourceThresholds": {
|
"resourceThresholds": {
|
||||||
@@ -131,5 +131,5 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"savedAt": "2026-02-28T16:23:19.387Z"
|
"savedAt": "2026-03-09T15:43:00.933Z"
|
||||||
}
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
54612
|
31273
|
||||||
+13
-13
@@ -6,7 +6,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node .claude/helpers/hook-handler.cjs pre-bash",
|
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/helpers/hook-handler.cjs\" pre-bash",
|
||||||
"timeout": 5000
|
"timeout": 5000
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node .claude/helpers/hook-handler.cjs post-edit",
|
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/helpers/hook-handler.cjs\" post-edit",
|
||||||
"timeout": 10000
|
"timeout": 10000
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node .claude/helpers/hook-handler.cjs route",
|
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/helpers/hook-handler.cjs\" route",
|
||||||
"timeout": 10000
|
"timeout": 10000
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -40,12 +40,12 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node .claude/helpers/hook-handler.cjs session-restore",
|
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/helpers/hook-handler.cjs\" session-restore",
|
||||||
"timeout": 15000
|
"timeout": 15000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node .claude/helpers/auto-memory-hook.mjs import",
|
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/helpers/auto-memory-hook.mjs\" import",
|
||||||
"timeout": 8000
|
"timeout": 8000
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node .claude/helpers/hook-handler.cjs session-end",
|
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/helpers/hook-handler.cjs\" session-end",
|
||||||
"timeout": 10000
|
"timeout": 10000
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node .claude/helpers/auto-memory-hook.mjs sync",
|
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/helpers/auto-memory-hook.mjs\" sync",
|
||||||
"timeout": 10000
|
"timeout": 10000
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -79,11 +79,11 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node .claude/helpers/hook-handler.cjs compact-manual"
|
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/helpers/hook-handler.cjs\" compact-manual"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node .claude/helpers/hook-handler.cjs session-end",
|
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/helpers/hook-handler.cjs\" session-end",
|
||||||
"timeout": 5000
|
"timeout": 5000
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -93,11 +93,11 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node .claude/helpers/hook-handler.cjs compact-auto"
|
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/helpers/hook-handler.cjs\" compact-auto"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node .claude/helpers/hook-handler.cjs session-end",
|
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/helpers/hook-handler.cjs\" session-end",
|
||||||
"timeout": 6000
|
"timeout": 6000
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node .claude/helpers/hook-handler.cjs status",
|
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/helpers/hook-handler.cjs\" status",
|
||||||
"timeout": 3000
|
"timeout": 3000
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
},
|
},
|
||||||
"statusLine": {
|
"statusLine": {
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node .claude/helpers/statusline.cjs"
|
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/helpers/statusline.cjs\""
|
||||||
},
|
},
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
|
|||||||
+17
-4
@@ -78,6 +78,17 @@ docker pull ruvnet/wifi-densepose:latest
|
|||||||
|
|
||||||
Multi-architecture image (amd64 + arm64). Works on Intel/AMD and Apple Silicon Macs. Contains the Rust sensing server, Three.js UI, and all signal processing.
|
Multi-architecture image (amd64 + arm64). Works on Intel/AMD and Apple Silicon Macs. Contains the Rust sensing server, Three.js UI, and all signal processing.
|
||||||
|
|
||||||
|
**Data source selection:** Use the `CSI_SOURCE` environment variable to select the sensing mode:
|
||||||
|
|
||||||
|
| Value | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `auto` | (default) Probe for ESP32 on UDP 5005, fall back to simulation |
|
||||||
|
| `esp32` | Receive real CSI frames from ESP32 devices over UDP |
|
||||||
|
| `simulated` | Generate synthetic CSI frames (no hardware required) |
|
||||||
|
| `wifi` | Host Wi-Fi RSSI (not available inside containers) |
|
||||||
|
|
||||||
|
Example: `docker run -e CSI_SOURCE=esp32 -p 3000:3000 -p 5005:5005/udp ruvnet/wifi-densepose:latest`
|
||||||
|
|
||||||
### From Source (Rust)
|
### From Source (Rust)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -267,8 +278,8 @@ Real Channel State Information at 20 Hz with 56-192 subcarriers. Required for po
|
|||||||
# From source
|
# From source
|
||||||
./target/release/sensing-server --source esp32 --udp-port 5005 --http-port 3000 --ws-port 3001
|
./target/release/sensing-server --source esp32 --udp-port 5005 --http-port 3000 --ws-port 3001
|
||||||
|
|
||||||
# Docker
|
# Docker (use CSI_SOURCE environment variable)
|
||||||
docker run -p 3000:3000 -p 3001:3001 -p 5005:5005/udp ruvnet/wifi-densepose:latest --source esp32
|
docker run -p 3000:3000 -p 3001:3001 -p 5005:5005/udp -e CSI_SOURCE=esp32 ruvnet/wifi-densepose:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
The ESP32 nodes stream binary CSI frames over UDP to port 5005. See [Hardware Setup](#esp32-s3-mesh) for flashing instructions.
|
The ESP32 nodes stream binary CSI frames over UDP to port 5005. See [Hardware Setup](#esp32-s3-mesh) for flashing instructions.
|
||||||
@@ -679,9 +690,11 @@ Download the dataset files and place them in a `data/` directory.
|
|||||||
./target/release/sensing-server --train --dataset data/ --dataset-type mmfi --epochs 100 --save-rvf model.rvf
|
./target/release/sensing-server --train --dataset data/ --dataset-type mmfi --epochs 100 --save-rvf model.rvf
|
||||||
|
|
||||||
# Via Docker (mount your data directory)
|
# Via Docker (mount your data directory)
|
||||||
|
# Note: Training mode requires overriding the default entrypoint
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
-v $(pwd)/data:/data \
|
-v $(pwd)/data:/data \
|
||||||
-v $(pwd)/output:/output \
|
-v $(pwd)/output:/output \
|
||||||
|
--entrypoint /app/sensing-server \
|
||||||
ruvnet/wifi-densepose:latest \
|
ruvnet/wifi-densepose:latest \
|
||||||
--train --dataset /data --epochs 100 --export-rvf /output/model.rvf
|
--train --dataset /data --epochs 100 --export-rvf /output/model.rvf
|
||||||
```
|
```
|
||||||
@@ -885,8 +898,8 @@ Binary size: 777 KB (24% free in the 1 MB app partition).
|
|||||||
# From source
|
# From source
|
||||||
./target/release/sensing-server --source esp32 --udp-port 5005 --http-port 3000 --ws-port 3001
|
./target/release/sensing-server --source esp32 --udp-port 5005 --http-port 3000 --ws-port 3001
|
||||||
|
|
||||||
# Docker
|
# Docker (use CSI_SOURCE environment variable)
|
||||||
docker run -p 3000:3000 -p 3001:3001 -p 5005:5005/udp ruvnet/wifi-densepose:latest --source esp32
|
docker run -p 3000:3000 -p 3001:3001 -p 5005:5005/udp -e CSI_SOURCE=esp32 ruvnet/wifi-densepose:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
See [ADR-018](../docs/adr/ADR-018-esp32-dev-implementation.md), [ADR-029](../docs/adr/ADR-029-ruvsense-multistatic-sensing-mode.md), and [Tutorial #34](https://github.com/ruvnet/RuView/issues/34).
|
See [ADR-018](../docs/adr/ADR-018-esp32-dev-implementation.md), [ADR-029](../docs/adr/ADR-029-ruvsense-multistatic-sensing-mode.md), and [Tutorial #34](https://github.com/ruvnet/RuView/issues/34).
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
{
|
||||||
|
"running": true,
|
||||||
|
"startedAt": "2026-03-10T14:22:41.948Z",
|
||||||
|
"workers": {
|
||||||
|
"map": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-03-10T14:22:41.948Z"
|
||||||
|
},
|
||||||
|
"audit": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-03-10T14:24:41.948Z"
|
||||||
|
},
|
||||||
|
"optimize": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-03-10T14:26:41.948Z"
|
||||||
|
},
|
||||||
|
"consolidate": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-03-10T14:28:41.949Z"
|
||||||
|
},
|
||||||
|
"testgaps": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-03-10T14:30:41.949Z"
|
||||||
|
},
|
||||||
|
"predict": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false
|
||||||
|
},
|
||||||
|
"document": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"autoStart": false,
|
||||||
|
"logDir": "/Users/cohen/GitHub/ruvnet/RuView/firmware/esp32-csi-node/.claude-flow/logs",
|
||||||
|
"stateFile": "/Users/cohen/GitHub/ruvnet/RuView/firmware/esp32-csi-node/.claude-flow/daemon-state.json",
|
||||||
|
"maxConcurrent": 2,
|
||||||
|
"workerTimeoutMs": 300000,
|
||||||
|
"resourceThresholds": {
|
||||||
|
"maxCpuLoad": 2,
|
||||||
|
"minFreeMemoryPercent": 20
|
||||||
|
},
|
||||||
|
"workers": [
|
||||||
|
{
|
||||||
|
"type": "map",
|
||||||
|
"intervalMs": 900000,
|
||||||
|
"offsetMs": 0,
|
||||||
|
"priority": "normal",
|
||||||
|
"description": "Codebase mapping",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "audit",
|
||||||
|
"intervalMs": 600000,
|
||||||
|
"offsetMs": 120000,
|
||||||
|
"priority": "critical",
|
||||||
|
"description": "Security analysis",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "optimize",
|
||||||
|
"intervalMs": 900000,
|
||||||
|
"offsetMs": 240000,
|
||||||
|
"priority": "high",
|
||||||
|
"description": "Performance optimization",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consolidate",
|
||||||
|
"intervalMs": 1800000,
|
||||||
|
"offsetMs": 360000,
|
||||||
|
"priority": "low",
|
||||||
|
"description": "Memory consolidation",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "testgaps",
|
||||||
|
"intervalMs": 1200000,
|
||||||
|
"offsetMs": 480000,
|
||||||
|
"priority": "normal",
|
||||||
|
"description": "Test coverage analysis",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "predict",
|
||||||
|
"intervalMs": 600000,
|
||||||
|
"offsetMs": 0,
|
||||||
|
"priority": "low",
|
||||||
|
"description": "Predictive preloading",
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "document",
|
||||||
|
"intervalMs": 3600000,
|
||||||
|
"offsetMs": 0,
|
||||||
|
"priority": "low",
|
||||||
|
"description": "Auto-documentation",
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"savedAt": "2026-03-10T14:22:41.949Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{"type":"edit","file":"unknown","timestamp":1773152422749,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773152444021,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773152460956,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773152493971,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773152501432,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773152510853,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773152596890,"sessionId":null}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"id": "session-1773152560779",
|
||||||
|
"startedAt": "2026-03-10T14:22:40.779Z",
|
||||||
|
"cwd": "/Users/cohen/GitHub/ruvnet/RuView/firmware/esp32-csi-node",
|
||||||
|
"context": {},
|
||||||
|
"metrics": {
|
||||||
|
"edits": 1,
|
||||||
|
"commands": 0,
|
||||||
|
"tasks": 0,
|
||||||
|
"errors": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -3,3 +3,8 @@
|
|||||||
{"type":"edit","file":"unknown","timestamp":1772820472219,"sessionId":null}
|
{"type":"edit","file":"unknown","timestamp":1772820472219,"sessionId":null}
|
||||||
{"type":"edit","file":"unknown","timestamp":1772832571444,"sessionId":null}
|
{"type":"edit","file":"unknown","timestamp":1772832571444,"sessionId":null}
|
||||||
{"type":"edit","file":"unknown","timestamp":1772832585997,"sessionId":null}
|
{"type":"edit","file":"unknown","timestamp":1772832585997,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773099593107,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773115162931,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773115172336,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773147087836,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773149448951,"sessionId":null}
|
||||||
|
|||||||
Generated
+22
@@ -2870,6 +2870,26 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libudev"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"libudev-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libudev-sys"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@@ -5614,6 +5634,7 @@ dependencies = [
|
|||||||
"core-foundation",
|
"core-foundation",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"io-kit-sys",
|
"io-kit-sys",
|
||||||
|
"libudev",
|
||||||
"mach2",
|
"mach2",
|
||||||
"nix 0.26.4",
|
"nix 0.26.4",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
@@ -7634,6 +7655,7 @@ dependencies = [
|
|||||||
"reqwest 0.12.28",
|
"reqwest 0.12.28",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serialport",
|
||||||
"sha2",
|
"sha2",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"tauri",
|
"tauri",
|
||||||
|
|||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
{
|
||||||
|
"running": true,
|
||||||
|
"startedAt": "2026-03-09T23:56:03.574Z",
|
||||||
|
"workers": {
|
||||||
|
"map": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-03-09T23:56:03.574Z"
|
||||||
|
},
|
||||||
|
"audit": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-03-09T23:58:03.574Z"
|
||||||
|
},
|
||||||
|
"optimize": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-03-10T00:00:03.574Z"
|
||||||
|
},
|
||||||
|
"consolidate": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-03-10T00:02:03.574Z"
|
||||||
|
},
|
||||||
|
"testgaps": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-03-10T00:04:03.574Z"
|
||||||
|
},
|
||||||
|
"predict": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false
|
||||||
|
},
|
||||||
|
"document": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"autoStart": false,
|
||||||
|
"logDir": "/Users/cohen/GitHub/ruvnet/RuView/rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/.claude-flow/logs",
|
||||||
|
"stateFile": "/Users/cohen/GitHub/ruvnet/RuView/rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/.claude-flow/daemon-state.json",
|
||||||
|
"maxConcurrent": 2,
|
||||||
|
"workerTimeoutMs": 300000,
|
||||||
|
"resourceThresholds": {
|
||||||
|
"maxCpuLoad": 2,
|
||||||
|
"minFreeMemoryPercent": 20
|
||||||
|
},
|
||||||
|
"workers": [
|
||||||
|
{
|
||||||
|
"type": "map",
|
||||||
|
"intervalMs": 900000,
|
||||||
|
"offsetMs": 0,
|
||||||
|
"priority": "normal",
|
||||||
|
"description": "Codebase mapping",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "audit",
|
||||||
|
"intervalMs": 600000,
|
||||||
|
"offsetMs": 120000,
|
||||||
|
"priority": "critical",
|
||||||
|
"description": "Security analysis",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "optimize",
|
||||||
|
"intervalMs": 900000,
|
||||||
|
"offsetMs": 240000,
|
||||||
|
"priority": "high",
|
||||||
|
"description": "Performance optimization",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consolidate",
|
||||||
|
"intervalMs": 1800000,
|
||||||
|
"offsetMs": 360000,
|
||||||
|
"priority": "low",
|
||||||
|
"description": "Memory consolidation",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "testgaps",
|
||||||
|
"intervalMs": 1200000,
|
||||||
|
"offsetMs": 480000,
|
||||||
|
"priority": "normal",
|
||||||
|
"description": "Test coverage analysis",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "predict",
|
||||||
|
"intervalMs": 600000,
|
||||||
|
"offsetMs": 0,
|
||||||
|
"priority": "low",
|
||||||
|
"description": "Predictive preloading",
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "document",
|
||||||
|
"intervalMs": 3600000,
|
||||||
|
"offsetMs": 0,
|
||||||
|
"priority": "low",
|
||||||
|
"description": "Auto-documentation",
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"savedAt": "2026-03-09T23:56:03.574Z"
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
{"type":"edit","file":"unknown","timestamp":1773100520674,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773100630628,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773100635269,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773100648222,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773100660593,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773100670480,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773100765961,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773100793408,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773100801110,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773100806887,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773100820942,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773100857691,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773100894224,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773100911798,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773101430507,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773101470221,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773101478246,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773103575668,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773103693989,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773115108388,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773115362485,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773115372676,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773115388605,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773115394377,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773115415015,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773115600459,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773146102258,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773146113449,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773146119695,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773146128174,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773146133721,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773146150082,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773146337071,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773150581963,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773150596765,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773152997925,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773153073387,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773153109436,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773153121443,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773153290476,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773153290781,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773153291056,"sessionId":null}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"id": "session-1773150558480",
|
||||||
|
"startedAt": "2026-03-10T13:49:18.480Z",
|
||||||
|
"cwd": "/Users/cohen/GitHub/ruvnet/RuView/rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop",
|
||||||
|
"context": {},
|
||||||
|
"metrics": {
|
||||||
|
"edits": 9,
|
||||||
|
"commands": 0,
|
||||||
|
"tasks": 0,
|
||||||
|
"errors": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"id": "session-1773100562538",
|
||||||
|
"startedAt": "2026-03-09T23:56:02.538Z",
|
||||||
|
"cwd": "/Users/cohen/GitHub/ruvnet/RuView/rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop",
|
||||||
|
"context": {},
|
||||||
|
"metrics": {
|
||||||
|
"edits": 13,
|
||||||
|
"commands": 0,
|
||||||
|
"tasks": 0,
|
||||||
|
"errors": 0
|
||||||
|
},
|
||||||
|
"endedAt": "2026-03-10T00:07:15.557Z",
|
||||||
|
"duration": 673020
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"id": "session-1773101285009",
|
||||||
|
"startedAt": "2026-03-10T00:08:05.009Z",
|
||||||
|
"cwd": "/Users/cohen/GitHub/ruvnet/RuView/rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop",
|
||||||
|
"context": {},
|
||||||
|
"metrics": {
|
||||||
|
"edits": 19,
|
||||||
|
"commands": 0,
|
||||||
|
"tasks": 0,
|
||||||
|
"errors": 0
|
||||||
|
},
|
||||||
|
"endedAt": "2026-03-10T13:48:30.150Z",
|
||||||
|
"duration": 49225141
|
||||||
|
}
|
||||||
@@ -56,6 +56,11 @@ hex = "0.4"
|
|||||||
# Regex for parsing espflash output
|
# Regex for parsing espflash output
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
|
|
||||||
|
# Serial port for WiFi configuration
|
||||||
|
serialport.workspace = true
|
||||||
|
|
||||||
# Unix signals for graceful process termination
|
# Unix signals for graceful process termination
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
|||||||
+2630
File diff suppressed because it is too large
Load Diff
@@ -411,6 +411,91 @@ fn is_esp32_compatible(vid: u16, pid: u16) -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configure WiFi credentials on an ESP32 via serial port.
|
||||||
|
///
|
||||||
|
/// Sends WiFi credentials to the ESP32 using a simple serial protocol.
|
||||||
|
/// The ESP32 firmware should accept: `wifi_config <ssid> <password>\n`
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn configure_esp32_wifi(
|
||||||
|
port: String,
|
||||||
|
ssid: String,
|
||||||
|
password: String,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
tracing::info!("Configuring WiFi on port: {}", port);
|
||||||
|
|
||||||
|
// Open serial port
|
||||||
|
let mut serial = serialport::new(&port, 115200)
|
||||||
|
.timeout(Duration::from_secs(3))
|
||||||
|
.open()
|
||||||
|
.map_err(|e| format!("Failed to open port {}: {}", port, e))?;
|
||||||
|
|
||||||
|
// Wait for ESP32 to be ready
|
||||||
|
std::thread::sleep(Duration::from_millis(500));
|
||||||
|
|
||||||
|
// Try multiple command formats that different firmware versions might accept
|
||||||
|
let commands = [
|
||||||
|
format!("wifi_config {} {}\r\n", ssid, password),
|
||||||
|
format!("wifi {} {}\r\n", ssid, password),
|
||||||
|
format!("set ssid {}\r\n", ssid),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut response = String::new();
|
||||||
|
let mut buf = [0u8; 512];
|
||||||
|
|
||||||
|
for cmd in &commands {
|
||||||
|
// Clear any pending data
|
||||||
|
let _ = serial.read(&mut buf);
|
||||||
|
|
||||||
|
// Send command
|
||||||
|
serial.write_all(cmd.as_bytes())
|
||||||
|
.map_err(|e| format!("Failed to write: {}", e))?;
|
||||||
|
serial.flush().map_err(|e| format!("Failed to flush: {}", e))?;
|
||||||
|
|
||||||
|
// Wait and read response
|
||||||
|
std::thread::sleep(Duration::from_millis(500));
|
||||||
|
|
||||||
|
match serial.read(&mut buf) {
|
||||||
|
Ok(n) if n > 0 => {
|
||||||
|
let text = String::from_utf8_lossy(&buf[..n]).to_string();
|
||||||
|
response.push_str(&text);
|
||||||
|
|
||||||
|
// Check for success indicators
|
||||||
|
if text.to_lowercase().contains("ok")
|
||||||
|
|| text.to_lowercase().contains("saved")
|
||||||
|
|| text.to_lowercase().contains("configured") {
|
||||||
|
tracing::info!("WiFi config successful: {}", text.trim());
|
||||||
|
return Ok(format!("WiFi configured! Response: {}", text.trim()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also try to send password separately if ssid command was sent
|
||||||
|
let pwd_cmd = format!("set password {}\r\n", password);
|
||||||
|
let _ = serial.write_all(pwd_cmd.as_bytes());
|
||||||
|
let _ = serial.flush();
|
||||||
|
std::thread::sleep(Duration::from_millis(300));
|
||||||
|
if let Ok(n) = serial.read(&mut buf) {
|
||||||
|
if n > 0 {
|
||||||
|
response.push_str(&String::from_utf8_lossy(&buf[..n]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send reboot command
|
||||||
|
let _ = serial.write_all(b"reboot\r\n");
|
||||||
|
let _ = serial.flush();
|
||||||
|
|
||||||
|
if response.is_empty() {
|
||||||
|
Ok("Commands sent. ESP32 may need manual reboot to apply WiFi settings.".to_string())
|
||||||
|
} else {
|
||||||
|
Ok(format!("Commands sent. Response: {}", response.trim()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct SerialPortInfo {
|
pub struct SerialPortInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub fn run() {
|
|||||||
// Discovery
|
// Discovery
|
||||||
discovery::discover_nodes,
|
discovery::discover_nodes,
|
||||||
discovery::list_serial_ports,
|
discovery::list_serial_ports,
|
||||||
|
discovery::configure_esp32_wifi,
|
||||||
// Flash
|
// Flash
|
||||||
flash::flash_firmware,
|
flash::flash_firmware,
|
||||||
flash::flash_progress,
|
flash::flash_progress,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-config-schema/schema.json",
|
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-config-schema/schema.json",
|
||||||
"productName": "RuView Desktop",
|
"productName": "RuView Desktop",
|
||||||
"version": "0.4.3",
|
"version": "0.4.4",
|
||||||
"identifier": "net.ruv.ruview",
|
"identifier": "net.ruv.ruview",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "ui/dist",
|
"frontendDist": "ui/dist",
|
||||||
@@ -30,9 +30,6 @@
|
|||||||
"icons/128x128@2x.png",
|
"icons/128x128@2x.png",
|
||||||
"icons/icon.icns",
|
"icons/icon.icns",
|
||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
],
|
]
|
||||||
"resources": {
|
|
||||||
"../../target/release/sensing-server": "bin/sensing-server"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,420 @@
|
|||||||
|
//! Integration tests for all Tauri API commands
|
||||||
|
//!
|
||||||
|
//! Tests the actual command implementations without the Tauri runtime.
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Discovery Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serial_port_detection_logic() {
|
||||||
|
// Test ESP32 VID/PID detection
|
||||||
|
// CP210x (Silicon Labs)
|
||||||
|
assert!(is_esp32_vid_pid(0x10C4, 0xEA60), "CP2102 should be detected");
|
||||||
|
assert!(is_esp32_vid_pid(0x10C4, 0xEA70), "CP2104 should be detected");
|
||||||
|
|
||||||
|
// CH340/CH341 (QinHeng)
|
||||||
|
assert!(is_esp32_vid_pid(0x1A86, 0x7523), "CH340 should be detected");
|
||||||
|
assert!(is_esp32_vid_pid(0x1A86, 0x5523), "CH341 should be detected");
|
||||||
|
|
||||||
|
// FTDI
|
||||||
|
assert!(is_esp32_vid_pid(0x0403, 0x6001), "FTDI FT232 should be detected");
|
||||||
|
assert!(is_esp32_vid_pid(0x0403, 0x6010), "FTDI FT2232 should be detected");
|
||||||
|
|
||||||
|
// ESP32 native USB
|
||||||
|
assert!(is_esp32_vid_pid(0x303A, 0x1001), "ESP32-S2/S3 native should be detected");
|
||||||
|
|
||||||
|
// Unknown device
|
||||||
|
assert!(!is_esp32_vid_pid(0x0000, 0x0000), "Unknown VID/PID should not be detected");
|
||||||
|
assert!(!is_esp32_vid_pid(0x1234, 0x5678), "Random VID/PID should not be detected");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_esp32_vid_pid(vid: u16, pid: u16) -> bool {
|
||||||
|
// CP210x (Silicon Labs)
|
||||||
|
if vid == 0x10C4 && (pid == 0xEA60 || pid == 0xEA70) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// CH340/CH341 (QinHeng)
|
||||||
|
if vid == 0x1A86 && (pid == 0x7523 || pid == 0x5523) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// FTDI
|
||||||
|
if vid == 0x0403 && (pid == 0x6001 || pid == 0x6010 || pid == 0x6011 || pid == 0x6014 || pid == 0x6015) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// ESP32-S2/S3 native USB
|
||||||
|
if vid == 0x303A {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_beacon_parsing() {
|
||||||
|
let data = b"RUVIEW_BEACON|AA:BB:CC:DD:EE:FF|1|0.3.0|esp32s3|coordinator|0|4";
|
||||||
|
let text = std::str::from_utf8(data).unwrap();
|
||||||
|
let parts: Vec<&str> = text.split('|').collect();
|
||||||
|
|
||||||
|
assert_eq!(parts.len(), 8);
|
||||||
|
assert_eq!(parts[0], "RUVIEW_BEACON");
|
||||||
|
assert_eq!(parts[1], "AA:BB:CC:DD:EE:FF");
|
||||||
|
assert_eq!(parts[2], "1");
|
||||||
|
assert_eq!(parts[3], "0.3.0");
|
||||||
|
assert_eq!(parts[4], "esp32s3");
|
||||||
|
assert_eq!(parts[5], "coordinator");
|
||||||
|
assert_eq!(parts[6], "0");
|
||||||
|
assert_eq!(parts[7], "4");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Settings Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_settings_structure() {
|
||||||
|
use wifi_densepose_desktop::commands::settings::AppSettings;
|
||||||
|
|
||||||
|
let settings = AppSettings::default();
|
||||||
|
|
||||||
|
// Check default values
|
||||||
|
assert!(!settings.theme.is_empty(), "Theme should have a default");
|
||||||
|
assert!(settings.discover_interval_ms > 0, "Discovery interval should be positive");
|
||||||
|
assert!(settings.auto_discover, "Auto-discover should default to true");
|
||||||
|
assert_eq!(settings.server_http_port, 8080);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_settings_serialization() {
|
||||||
|
use wifi_densepose_desktop::commands::settings::AppSettings;
|
||||||
|
|
||||||
|
let settings = AppSettings::default();
|
||||||
|
let json = serde_json::to_string(&settings).expect("Should serialize");
|
||||||
|
let restored: AppSettings = serde_json::from_str(&json).expect("Should deserialize");
|
||||||
|
|
||||||
|
assert_eq!(settings.theme, restored.theme);
|
||||||
|
assert_eq!(settings.server_http_port, restored.server_http_port);
|
||||||
|
assert_eq!(settings.discover_interval_ms, restored.discover_interval_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Server Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_server_state_default() {
|
||||||
|
use wifi_densepose_desktop::state::ServerState;
|
||||||
|
|
||||||
|
let server = ServerState::default();
|
||||||
|
assert!(!server.running, "Server should not be running by default");
|
||||||
|
assert!(server.pid.is_none());
|
||||||
|
assert!(server.http_port.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Flash Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_chip_variants() {
|
||||||
|
use wifi_densepose_desktop::domain::node::Chip;
|
||||||
|
|
||||||
|
let chips = vec![
|
||||||
|
Chip::Esp32,
|
||||||
|
Chip::Esp32s2,
|
||||||
|
Chip::Esp32s3,
|
||||||
|
Chip::Esp32c3,
|
||||||
|
Chip::Esp32c6,
|
||||||
|
];
|
||||||
|
|
||||||
|
for chip in chips {
|
||||||
|
let name = format!("{:?}", chip).to_lowercase();
|
||||||
|
assert!(name.starts_with("esp32"), "All chips should be ESP32 variants");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_progress_parsing() {
|
||||||
|
// Test espflash progress output parsing
|
||||||
|
let output = "Flashing... [===> ] 35%";
|
||||||
|
let re = regex::Regex::new(r"(\d+)%").unwrap();
|
||||||
|
|
||||||
|
if let Some(caps) = re.captures(output) {
|
||||||
|
let pct: u8 = caps[1].parse().unwrap();
|
||||||
|
assert_eq!(pct, 35);
|
||||||
|
} else {
|
||||||
|
panic!("Should parse percentage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OTA Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sha256_hash() {
|
||||||
|
use sha2::{Sha256, Digest};
|
||||||
|
|
||||||
|
let data = b"test firmware data";
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(data);
|
||||||
|
let hash = hasher.finalize();
|
||||||
|
let hex = hex::encode(hash);
|
||||||
|
|
||||||
|
assert_eq!(hex.len(), 64, "SHA256 should produce 64 hex characters");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hmac_signature() {
|
||||||
|
use hmac::{Hmac, Mac};
|
||||||
|
use sha2::Sha256;
|
||||||
|
|
||||||
|
type HmacSha256 = Hmac<Sha256>;
|
||||||
|
|
||||||
|
let key = b"test_psk_key";
|
||||||
|
let data = b"firmware_hash";
|
||||||
|
|
||||||
|
let mut mac = HmacSha256::new_from_slice(key).expect("HMAC can take key of any size");
|
||||||
|
mac.update(data);
|
||||||
|
let result = mac.finalize();
|
||||||
|
let signature = hex::encode(result.into_bytes());
|
||||||
|
|
||||||
|
assert_eq!(signature.len(), 64, "HMAC-SHA256 should produce 64 hex characters");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Provision Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nvs_config_format() {
|
||||||
|
// Test CSV format for NVS partition
|
||||||
|
let csv = "key,type,encoding,value\ncsi_cfg,namespace,,\nssid,data,string,TestNetwork\npassword,data,string,TestPass123\n";
|
||||||
|
|
||||||
|
let lines: Vec<&str> = csv.lines().collect();
|
||||||
|
assert_eq!(lines.len(), 4);
|
||||||
|
assert!(lines[0].starts_with("key,type"));
|
||||||
|
assert!(lines[1].contains("namespace"));
|
||||||
|
assert!(lines[2].contains("ssid"));
|
||||||
|
assert!(lines[3].contains("password"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mesh_config_generation() {
|
||||||
|
// Test that mesh configs have required fields
|
||||||
|
let config = serde_json::json!({
|
||||||
|
"node_id": 1,
|
||||||
|
"mesh_role": "node",
|
||||||
|
"tdm_slot": 0,
|
||||||
|
"tdm_total": 4,
|
||||||
|
"ssid": "TestNetwork",
|
||||||
|
"password": "TestPass",
|
||||||
|
"coordinator_ip": "192.168.1.100"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(config.get("node_id").is_some());
|
||||||
|
assert!(config.get("mesh_role").is_some());
|
||||||
|
assert!(config.get("ssid").is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// WASM Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wasm_magic_bytes() {
|
||||||
|
// WebAssembly magic bytes: \0asm
|
||||||
|
let wasm_header: [u8; 4] = [0x00, 0x61, 0x73, 0x6D];
|
||||||
|
|
||||||
|
assert_eq!(wasm_header[0], 0x00);
|
||||||
|
assert_eq!(wasm_header[1], 0x61); // 'a'
|
||||||
|
assert_eq!(wasm_header[2], 0x73); // 's'
|
||||||
|
assert_eq!(wasm_header[3], 0x6D); // 'm'
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wasm_version() {
|
||||||
|
// WASM version 1
|
||||||
|
let wasm_version: [u8; 4] = [0x01, 0x00, 0x00, 0x00];
|
||||||
|
|
||||||
|
let version = u32::from_le_bytes(wasm_version);
|
||||||
|
assert_eq!(version, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// State Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_app_state_initialization() {
|
||||||
|
use wifi_densepose_desktop::state::AppState;
|
||||||
|
|
||||||
|
let state = AppState::default();
|
||||||
|
|
||||||
|
// Check that all state components initialize correctly
|
||||||
|
let discovery = state.discovery.lock().unwrap();
|
||||||
|
assert!(discovery.nodes.is_empty(), "Should start with no nodes");
|
||||||
|
drop(discovery);
|
||||||
|
|
||||||
|
let flash = state.flash.lock().unwrap();
|
||||||
|
assert_eq!(flash.phase, "", "Should start with empty phase");
|
||||||
|
assert_eq!(flash.progress_pct, 0.0);
|
||||||
|
drop(flash);
|
||||||
|
|
||||||
|
let server = state.server.lock().unwrap();
|
||||||
|
assert!(!server.running, "Server should not be running initially");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Domain Model Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_health_status_variants() {
|
||||||
|
use wifi_densepose_desktop::domain::node::HealthStatus;
|
||||||
|
|
||||||
|
let statuses = vec![
|
||||||
|
HealthStatus::Online,
|
||||||
|
HealthStatus::Degraded,
|
||||||
|
HealthStatus::Offline,
|
||||||
|
];
|
||||||
|
|
||||||
|
for status in statuses {
|
||||||
|
let json = serde_json::to_string(&status).expect("Should serialize");
|
||||||
|
assert!(!json.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_discovery_method_variants() {
|
||||||
|
use wifi_densepose_desktop::domain::node::DiscoveryMethod;
|
||||||
|
|
||||||
|
let methods = vec![
|
||||||
|
DiscoveryMethod::Mdns,
|
||||||
|
DiscoveryMethod::UdpProbe,
|
||||||
|
DiscoveryMethod::Manual,
|
||||||
|
DiscoveryMethod::HttpSweep,
|
||||||
|
];
|
||||||
|
|
||||||
|
for method in methods {
|
||||||
|
let json = serde_json::to_string(&method).expect("Should serialize");
|
||||||
|
assert!(!json.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mesh_role_variants() {
|
||||||
|
use wifi_densepose_desktop::domain::node::MeshRole;
|
||||||
|
|
||||||
|
let roles = vec![
|
||||||
|
MeshRole::Coordinator,
|
||||||
|
MeshRole::Aggregator,
|
||||||
|
MeshRole::Node,
|
||||||
|
];
|
||||||
|
|
||||||
|
for role in roles {
|
||||||
|
let json = serde_json::to_string(&role).expect("Should serialize");
|
||||||
|
assert!(!json.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// WiFi Config Tests (New Feature)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wifi_config_command_format() {
|
||||||
|
let ssid = "TestNetwork";
|
||||||
|
let password = "TestPass123";
|
||||||
|
|
||||||
|
// Test all command formats
|
||||||
|
let cmd1 = format!("wifi_config {} {}\r\n", ssid, password);
|
||||||
|
let cmd2 = format!("wifi {} {}\r\n", ssid, password);
|
||||||
|
let cmd3 = format!("set ssid {}\r\n", ssid);
|
||||||
|
let cmd4 = format!("set password {}\r\n", password);
|
||||||
|
|
||||||
|
assert!(cmd1.contains("wifi_config"));
|
||||||
|
assert!(cmd1.contains(ssid));
|
||||||
|
assert!(cmd1.contains(password));
|
||||||
|
assert!(cmd1.ends_with("\r\n"));
|
||||||
|
|
||||||
|
assert!(cmd2.starts_with("wifi "));
|
||||||
|
assert!(cmd3.starts_with("set ssid "));
|
||||||
|
assert!(cmd4.starts_with("set password "));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wifi_credentials_validation() {
|
||||||
|
// SSID: 1-32 characters
|
||||||
|
let valid_ssid = "MyNetwork";
|
||||||
|
let empty_ssid = "";
|
||||||
|
let long_ssid = "A".repeat(33);
|
||||||
|
|
||||||
|
assert!(!valid_ssid.is_empty() && valid_ssid.len() <= 32);
|
||||||
|
assert!(empty_ssid.is_empty());
|
||||||
|
assert!(long_ssid.len() > 32);
|
||||||
|
|
||||||
|
// Password: 8-63 characters for WPA2
|
||||||
|
let valid_pass = "password123";
|
||||||
|
let short_pass = "short";
|
||||||
|
let long_pass = "A".repeat(64);
|
||||||
|
|
||||||
|
assert!(valid_pass.len() >= 8 && valid_pass.len() <= 63);
|
||||||
|
assert!(short_pass.len() < 8);
|
||||||
|
assert!(long_pass.len() > 63);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Node Registry Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_node_registry() {
|
||||||
|
use wifi_densepose_desktop::domain::node::{
|
||||||
|
DiscoveredNode, MacAddress, NodeRegistry, HealthStatus, Chip, MeshRole, DiscoveryMethod
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut registry = NodeRegistry::new();
|
||||||
|
assert!(registry.is_empty());
|
||||||
|
|
||||||
|
let node = DiscoveredNode {
|
||||||
|
ip: "192.168.1.100".into(),
|
||||||
|
mac: Some("AA:BB:CC:DD:EE:FF".into()),
|
||||||
|
hostname: Some("csi-node-1".into()),
|
||||||
|
node_id: 1,
|
||||||
|
firmware_version: Some("0.3.0".into()),
|
||||||
|
health: HealthStatus::Online,
|
||||||
|
last_seen: "2024-01-01T00:00:00Z".into(),
|
||||||
|
chip: Chip::Esp32s3,
|
||||||
|
mesh_role: MeshRole::Node,
|
||||||
|
discovery_method: DiscoveryMethod::Mdns,
|
||||||
|
tdm_slot: Some(0),
|
||||||
|
tdm_total: Some(4),
|
||||||
|
edge_tier: None,
|
||||||
|
uptime_secs: Some(3600),
|
||||||
|
capabilities: None,
|
||||||
|
friendly_name: None,
|
||||||
|
notes: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.upsert(MacAddress::new("AA:BB:CC:DD:EE:FF"), node);
|
||||||
|
assert_eq!(registry.len(), 1);
|
||||||
|
|
||||||
|
let retrieved = registry.get(&MacAddress::new("AA:BB:CC:DD:EE:FF"));
|
||||||
|
assert!(retrieved.is_some());
|
||||||
|
assert_eq!(retrieved.unwrap().ip, "192.168.1.100");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// MAC Address Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mac_address() {
|
||||||
|
use wifi_densepose_desktop::domain::node::MacAddress;
|
||||||
|
|
||||||
|
let mac = MacAddress::new("AA:BB:CC:DD:EE:FF");
|
||||||
|
assert_eq!(mac.to_string(), "AA:BB:CC:DD:EE:FF");
|
||||||
|
|
||||||
|
let mac2 = MacAddress::new("aa:bb:cc:dd:ee:ff");
|
||||||
|
assert_ne!(mac, mac2); // Case sensitive comparison
|
||||||
|
}
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
{
|
||||||
|
"running": true,
|
||||||
|
"startedAt": "2026-03-10T00:49:11.921Z",
|
||||||
|
"workers": {
|
||||||
|
"map": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-03-10T00:49:11.921Z"
|
||||||
|
},
|
||||||
|
"audit": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-03-10T00:51:11.921Z"
|
||||||
|
},
|
||||||
|
"optimize": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-03-10T00:53:11.921Z"
|
||||||
|
},
|
||||||
|
"consolidate": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-03-10T00:55:11.921Z"
|
||||||
|
},
|
||||||
|
"testgaps": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false,
|
||||||
|
"nextRun": "2026-03-10T00:57:11.921Z"
|
||||||
|
},
|
||||||
|
"predict": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false
|
||||||
|
},
|
||||||
|
"document": {
|
||||||
|
"runCount": 0,
|
||||||
|
"successCount": 0,
|
||||||
|
"failureCount": 0,
|
||||||
|
"averageDurationMs": 0,
|
||||||
|
"isRunning": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"autoStart": false,
|
||||||
|
"logDir": "/Users/cohen/GitHub/ruvnet/RuView/rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/.claude-flow/logs",
|
||||||
|
"stateFile": "/Users/cohen/GitHub/ruvnet/RuView/rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/.claude-flow/daemon-state.json",
|
||||||
|
"maxConcurrent": 2,
|
||||||
|
"workerTimeoutMs": 300000,
|
||||||
|
"resourceThresholds": {
|
||||||
|
"maxCpuLoad": 2,
|
||||||
|
"minFreeMemoryPercent": 20
|
||||||
|
},
|
||||||
|
"workers": [
|
||||||
|
{
|
||||||
|
"type": "map",
|
||||||
|
"intervalMs": 900000,
|
||||||
|
"offsetMs": 0,
|
||||||
|
"priority": "normal",
|
||||||
|
"description": "Codebase mapping",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "audit",
|
||||||
|
"intervalMs": 600000,
|
||||||
|
"offsetMs": 120000,
|
||||||
|
"priority": "critical",
|
||||||
|
"description": "Security analysis",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "optimize",
|
||||||
|
"intervalMs": 900000,
|
||||||
|
"offsetMs": 240000,
|
||||||
|
"priority": "high",
|
||||||
|
"description": "Performance optimization",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consolidate",
|
||||||
|
"intervalMs": 1800000,
|
||||||
|
"offsetMs": 360000,
|
||||||
|
"priority": "low",
|
||||||
|
"description": "Memory consolidation",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "testgaps",
|
||||||
|
"intervalMs": 1200000,
|
||||||
|
"offsetMs": 480000,
|
||||||
|
"priority": "normal",
|
||||||
|
"description": "Test coverage analysis",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "predict",
|
||||||
|
"intervalMs": 600000,
|
||||||
|
"offsetMs": 0,
|
||||||
|
"priority": "low",
|
||||||
|
"description": "Predictive preloading",
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "document",
|
||||||
|
"intervalMs": 3600000,
|
||||||
|
"offsetMs": 0,
|
||||||
|
"priority": "low",
|
||||||
|
"description": "Auto-documentation",
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"savedAt": "2026-03-10T00:49:11.921Z"
|
||||||
|
}
|
||||||
+17
@@ -9,3 +9,20 @@
|
|||||||
{"type":"edit","file":"unknown","timestamp":1772835930809,"sessionId":null}
|
{"type":"edit","file":"unknown","timestamp":1772835930809,"sessionId":null}
|
||||||
{"type":"edit","file":"unknown","timestamp":1772835942468,"sessionId":null}
|
{"type":"edit","file":"unknown","timestamp":1772835942468,"sessionId":null}
|
||||||
{"type":"edit","file":"unknown","timestamp":1772835952451,"sessionId":null}
|
{"type":"edit","file":"unknown","timestamp":1772835952451,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773070971487,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773070977376,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773101503481,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773107530083,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773107530201,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773107530319,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773114830434,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773114834713,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773114838852,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773150617007,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773150621430,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773150628006,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773150640909,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773150672276,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773150677219,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773150683839,"sessionId":null}
|
||||||
|
{"type":"edit","file":"unknown","timestamp":1773150688912,"sessionId":null}
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"id": "session-1773103750755",
|
||||||
|
"startedAt": "2026-03-10T00:49:10.755Z",
|
||||||
|
"cwd": "/Users/cohen/GitHub/ruvnet/RuView/rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui",
|
||||||
|
"context": {},
|
||||||
|
"metrics": {
|
||||||
|
"edits": 14,
|
||||||
|
"commands": 0,
|
||||||
|
"tasks": 0,
|
||||||
|
"errors": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -53,6 +53,7 @@
|
|||||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@babel/code-frame": "^7.29.0",
|
||||||
"@babel/generator": "^7.29.0",
|
"@babel/generator": "^7.29.0",
|
||||||
@@ -1246,6 +1247,7 @@
|
|||||||
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
|
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
@@ -1315,6 +1317,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -1584,6 +1587,7 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -1625,6 +1629,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
},
|
},
|
||||||
@@ -1797,6 +1802,7 @@
|
|||||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.4.4",
|
"fdir": "^6.4.4",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ruview-desktop-ui",
|
"name": "ruview-desktop-ui",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.4.3",
|
"version": "0.4.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
+283
-17
@@ -49,6 +49,12 @@ const NetworkDiscovery: React.FC<NetworkDiscoveryProps> = ({ onNavigate }) => {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [selectedNode, setSelectedNode] = useState<DiscoveredNode | null>(null);
|
const [selectedNode, setSelectedNode] = useState<DiscoveredNode | null>(null);
|
||||||
const [filterOnline, setFilterOnline] = useState(false);
|
const [filterOnline, setFilterOnline] = useState(false);
|
||||||
|
// WiFi config state
|
||||||
|
const [wifiConfigPort, setWifiConfigPort] = useState<string | null>(null);
|
||||||
|
const [wifiSsid, setWifiSsid] = useState("");
|
||||||
|
const [wifiPassword, setWifiPassword] = useState("");
|
||||||
|
const [configuringWifi, setConfiguringWifi] = useState(false);
|
||||||
|
const [wifiResult, setWifiResult] = useState<string | null>(null);
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
// Manual add state
|
// Manual add state
|
||||||
const [manualIp, setManualIp] = useState("");
|
const [manualIp, setManualIp] = useState("");
|
||||||
@@ -83,6 +89,24 @@ const NetworkDiscovery: React.FC<NetworkDiscoveryProps> = ({ onNavigate }) => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const configureWifi = useCallback(async () => {
|
||||||
|
if (!wifiConfigPort || !wifiSsid) return;
|
||||||
|
setConfiguringWifi(true);
|
||||||
|
setWifiResult(null);
|
||||||
|
try {
|
||||||
|
const result = await invoke<string>("configure_esp32_wifi", {
|
||||||
|
port: wifiConfigPort,
|
||||||
|
ssid: wifiSsid,
|
||||||
|
password: wifiPassword,
|
||||||
|
});
|
||||||
|
setWifiResult(result);
|
||||||
|
} catch (err) {
|
||||||
|
setWifiResult(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
|
} finally {
|
||||||
|
setConfiguringWifi(false);
|
||||||
|
}
|
||||||
|
}, [wifiConfigPort, wifiSsid, wifiPassword]);
|
||||||
|
|
||||||
const addManualNode = useCallback(async () => {
|
const addManualNode = useCallback(async () => {
|
||||||
if (!manualIp.trim()) return;
|
if (!manualIp.trim()) return;
|
||||||
setAddingManual(true);
|
setAddingManual(true);
|
||||||
@@ -471,23 +495,47 @@ const NetworkDiscovery: React.FC<NetworkDiscoveryProps> = ({ onNavigate }) => {
|
|||||||
)}
|
)}
|
||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{port.is_esp32_compatible && onNavigate && (
|
<div style={{ display: "flex", gap: 6 }}>
|
||||||
<button
|
{port.is_esp32_compatible && (
|
||||||
onClick={() => onNavigate("flash")}
|
<button
|
||||||
style={{
|
onClick={() => {
|
||||||
padding: "4px 12px",
|
setWifiConfigPort(port.name);
|
||||||
background: "var(--accent)",
|
setWifiSsid("");
|
||||||
border: "none",
|
setWifiPassword("");
|
||||||
borderRadius: 4,
|
setWifiResult(null);
|
||||||
color: "#fff",
|
}}
|
||||||
fontSize: 11,
|
style={{
|
||||||
fontWeight: 600,
|
padding: "4px 10px",
|
||||||
cursor: "pointer",
|
background: "rgba(56, 139, 253, 0.15)",
|
||||||
}}
|
border: "1px solid rgba(56, 139, 253, 0.3)",
|
||||||
>
|
borderRadius: 4,
|
||||||
Flash →
|
color: "var(--accent)",
|
||||||
</button>
|
fontSize: 11,
|
||||||
)}
|
fontWeight: 600,
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
WiFi
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{port.is_esp32_compatible && onNavigate && (
|
||||||
|
<button
|
||||||
|
onClick={() => onNavigate("flash")}
|
||||||
|
style={{
|
||||||
|
padding: "4px 10px",
|
||||||
|
background: "var(--accent)",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: 4,
|
||||||
|
color: "#fff",
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 600,
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Flash
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
@@ -579,6 +627,224 @@ const NetworkDiscovery: React.FC<NetworkDiscoveryProps> = ({ onNavigate }) => {
|
|||||||
{selectedNode && (
|
{selectedNode && (
|
||||||
<NodeDetailModal node={selectedNode} onClose={() => setSelectedNode(null)} />
|
<NodeDetailModal node={selectedNode} onClose={() => setSelectedNode(null)} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* WiFi Configuration Modal */}
|
||||||
|
{wifiConfigPort && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
inset: 0,
|
||||||
|
background: "rgba(0,0,0,0.6)",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
zIndex: 1000,
|
||||||
|
padding: "var(--space-5)",
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
if (e.target === e.currentTarget && !configuringWifi) {
|
||||||
|
setWifiConfigPort(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: "var(--bg-surface)",
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: "var(--space-5)",
|
||||||
|
maxWidth: 420,
|
||||||
|
width: "100%",
|
||||||
|
border: "1px solid var(--border)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "start",
|
||||||
|
marginBottom: "var(--space-4)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h2 className="heading-md" style={{ margin: 0 }}>
|
||||||
|
Configure WiFi
|
||||||
|
</h2>
|
||||||
|
<p className="mono" style={{ color: "var(--text-muted)", marginTop: 4, fontSize: 13 }}>
|
||||||
|
{wifiConfigPort}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setWifiConfigPort(null)}
|
||||||
|
disabled={configuringWifi}
|
||||||
|
style={{
|
||||||
|
background: "none",
|
||||||
|
border: "none",
|
||||||
|
fontSize: 20,
|
||||||
|
cursor: configuringWifi ? "not-allowed" : "pointer",
|
||||||
|
color: "var(--text-muted)",
|
||||||
|
padding: 4,
|
||||||
|
opacity: configuringWifi ? 0.5 : 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "var(--space-3)" }}>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: "var(--text-secondary)",
|
||||||
|
marginBottom: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
WiFi SSID *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Your WiFi network name"
|
||||||
|
value={wifiSsid}
|
||||||
|
onChange={(e) => setWifiSsid(e.target.value)}
|
||||||
|
disabled={configuringWifi}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
padding: "10px 12px",
|
||||||
|
borderRadius: 6,
|
||||||
|
border: "1px solid var(--border)",
|
||||||
|
background: "var(--bg-base)",
|
||||||
|
color: "var(--text-primary)",
|
||||||
|
fontSize: 13,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: "var(--text-secondary)",
|
||||||
|
marginBottom: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
WiFi Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
placeholder="WiFi password"
|
||||||
|
value={wifiPassword}
|
||||||
|
onChange={(e) => setWifiPassword(e.target.value)}
|
||||||
|
disabled={configuringWifi}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
padding: "10px 12px",
|
||||||
|
borderRadius: 6,
|
||||||
|
border: "1px solid var(--border)",
|
||||||
|
background: "var(--bg-base)",
|
||||||
|
color: "var(--text-primary)",
|
||||||
|
fontSize: 13,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{wifiResult && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "var(--space-3)",
|
||||||
|
borderRadius: 6,
|
||||||
|
fontSize: 12,
|
||||||
|
background: wifiResult.startsWith("Error")
|
||||||
|
? "rgba(248, 81, 73, 0.1)"
|
||||||
|
: wifiResult.includes("configured") || wifiResult.includes("saved")
|
||||||
|
? "rgba(63, 185, 80, 0.1)"
|
||||||
|
: "rgba(56, 139, 253, 0.1)",
|
||||||
|
border: wifiResult.startsWith("Error")
|
||||||
|
? "1px solid rgba(248, 81, 73, 0.3)"
|
||||||
|
: wifiResult.includes("configured") || wifiResult.includes("saved")
|
||||||
|
? "1px solid rgba(63, 185, 80, 0.3)"
|
||||||
|
: "1px solid rgba(56, 139, 253, 0.3)",
|
||||||
|
color: wifiResult.startsWith("Error")
|
||||||
|
? "var(--status-error)"
|
||||||
|
: wifiResult.includes("configured") || wifiResult.includes("saved")
|
||||||
|
? "var(--status-online)"
|
||||||
|
: "var(--accent)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontWeight: 600, marginBottom: 6 }}>
|
||||||
|
{wifiResult.startsWith("Error") ? "Error" :
|
||||||
|
wifiResult.includes("configured") || wifiResult.includes("saved") ? "Success!" : "Commands Sent"}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontFamily: "var(--font-mono)", whiteSpace: "pre-wrap", maxHeight: 100, overflow: "auto" }}>
|
||||||
|
{wifiResult}
|
||||||
|
</div>
|
||||||
|
{!wifiResult.startsWith("Error") && !wifiResult.includes("configured") && (
|
||||||
|
<div style={{ marginTop: 8, fontSize: 11, color: "var(--text-secondary)" }}>
|
||||||
|
If the ESP32 doesn't connect, try pressing its Reset button or re-flashing with WiFi credentials in the firmware.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ display: "flex", gap: "var(--space-3)", marginTop: "var(--space-2)" }}>
|
||||||
|
<button
|
||||||
|
onClick={() => setWifiConfigPort(null)}
|
||||||
|
disabled={configuringWifi}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
padding: "10px 16px",
|
||||||
|
borderRadius: 6,
|
||||||
|
border: "1px solid var(--border)",
|
||||||
|
background: wifiResult ? "var(--accent)" : "transparent",
|
||||||
|
color: wifiResult ? "#fff" : "var(--text-secondary)",
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 600,
|
||||||
|
cursor: configuringWifi ? "not-allowed" : "pointer",
|
||||||
|
opacity: configuringWifi ? 0.5 : 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{wifiResult ? "Done" : "Cancel"}
|
||||||
|
</button>
|
||||||
|
{!wifiResult && (
|
||||||
|
<button
|
||||||
|
onClick={configureWifi}
|
||||||
|
disabled={!wifiSsid.trim() || configuringWifi}
|
||||||
|
className="btn-gradient"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
opacity: !wifiSsid.trim() || configuringWifi ? 0.5 : 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{configuringWifi ? "Configuring..." : "Configure WiFi"}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{wifiResult && !wifiResult.startsWith("Error") && (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setWifiResult(null);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
padding: "10px 16px",
|
||||||
|
borderRadius: 6,
|
||||||
|
border: "1px solid var(--border)",
|
||||||
|
background: "transparent",
|
||||||
|
color: "var(--text-secondary)",
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 600,
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Try Again
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Application version - single source of truth
|
// Application version - single source of truth
|
||||||
export const APP_VERSION = "0.4.3";
|
export const APP_VERSION = "0.4.4";
|
||||||
|
|||||||
Reference in New Issue
Block a user