ruvllm-esp32 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -94,13 +94,31 @@ function detectPort() {
94
94
 
95
95
  try {
96
96
  if (platform === 'win32') {
97
- // Windows: Look for COM ports
98
- const result = execSync('wmic path Win32_SerialPort get DeviceID', { encoding: 'utf8' });
99
- const ports = result.split('\n').filter(line => line.includes('COM')).map(line => line.trim());
100
- return ports[0] || 'COM3';
97
+ // Windows: Use PowerShell for better COM port detection
98
+ try {
99
+ const result = execSync(
100
+ 'powershell -Command "[System.IO.Ports.SerialPort]::GetPortNames() | Sort-Object { [int]($_ -replace \'COM\', \'\') }"',
101
+ { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
102
+ );
103
+ const ports = result.trim().split('\n').filter(p => p.match(/COM\d+/));
104
+ if (ports.length > 0) {
105
+ return ports[0].trim();
106
+ }
107
+ } catch {
108
+ // Fallback to wmic
109
+ const result = execSync('wmic path Win32_SerialPort get DeviceID 2>nul', { encoding: 'utf8' });
110
+ const ports = result.split('\n').filter(line => line.includes('COM')).map(line => line.trim());
111
+ if (ports.length > 0) return ports[0];
112
+ }
113
+ return 'COM3';
101
114
  } else if (platform === 'darwin') {
102
115
  // macOS
103
- const files = fs.readdirSync('/dev').filter(f => f.startsWith('cu.usbserial') || f.startsWith('cu.SLAB'));
116
+ const files = fs.readdirSync('/dev').filter(f =>
117
+ f.startsWith('cu.usbserial') ||
118
+ f.startsWith('cu.SLAB') ||
119
+ f.startsWith('cu.wchusbserial') ||
120
+ f.startsWith('cu.usbmodem')
121
+ );
104
122
  return files[0] ? `/dev/${files[0]}` : '/dev/cu.usbserial-0001';
105
123
  } else {
106
124
  // Linux
@@ -127,27 +145,52 @@ async function installToolchain() {
127
145
  const { platform } = detectPlatform();
128
146
 
129
147
  try {
130
- // Install espup
131
- logStep('Installing espup...');
132
148
  if (platform === 'win32') {
133
- execSync('cargo install espup', { stdio: 'inherit' });
134
- } else {
135
- execSync('curl -L https://github.com/esp-rs/espup/releases/latest/download/espup-x86_64-unknown-linux-gnu -o /tmp/espup && chmod +x /tmp/espup && /tmp/espup install', { stdio: 'inherit' });
136
- }
149
+ // Windows: Check if we have the PowerShell setup script
150
+ const scriptsDir = path.join(__dirname, '..', 'scripts', 'windows');
151
+ const setupScript = path.join(scriptsDir, 'setup.ps1');
137
152
 
138
- // Install espflash
139
- logStep('Installing espflash...');
140
- execSync('cargo install espflash ldproxy', { stdio: 'inherit' });
153
+ if (fs.existsSync(setupScript)) {
154
+ logStep('Running Windows setup script...');
155
+ execSync(`powershell -ExecutionPolicy Bypass -File "${setupScript}"`, { stdio: 'inherit' });
156
+ } else {
157
+ // Fallback: manual installation
158
+ logStep('Installing espup...');
141
159
 
142
- // Run espup install
143
- logStep('Setting up ESP32 toolchain...');
144
- execSync('espup install', { stdio: 'inherit' });
160
+ // Download espup for Windows
161
+ const espupUrl = 'https://github.com/esp-rs/espup/releases/latest/download/espup-x86_64-pc-windows-msvc.exe';
162
+ const espupPath = path.join(os.tmpdir(), 'espup.exe');
163
+
164
+ execSync(`powershell -Command "Invoke-WebRequest -Uri '${espupUrl}' -OutFile '${espupPath}'"`, { stdio: 'inherit' });
165
+
166
+ logStep('Running espup install...');
167
+ execSync(`"${espupPath}" install`, { stdio: 'inherit' });
168
+
169
+ // Install espflash
170
+ logStep('Installing espflash...');
171
+ execSync('cargo install espflash ldproxy', { stdio: 'inherit' });
172
+ }
173
+
174
+ logSuccess('Toolchain installed successfully!');
175
+ log('\nTo use the toolchain, run:', 'yellow');
176
+ log(' . .\\scripts\\windows\\env.ps1', 'cyan');
145
177
 
146
- logSuccess('Toolchain installed successfully!');
147
- log('\nPlease restart your terminal or run:', 'yellow');
148
- if (platform === 'win32') {
149
- log(' $env:PATH = [System.Environment]::GetEnvironmentVariable("Path","User")', 'cyan');
150
178
  } else {
179
+ // Linux/macOS
180
+ logStep('Installing espup...');
181
+ const arch = os.arch() === 'arm64' ? 'aarch64' : 'x86_64';
182
+ const binary = platform === 'darwin'
183
+ ? `espup-${arch}-apple-darwin`
184
+ : `espup-${arch}-unknown-linux-gnu`;
185
+
186
+ execSync(`curl -L https://github.com/esp-rs/espup/releases/latest/download/${binary} -o /tmp/espup && chmod +x /tmp/espup && /tmp/espup install`, { stdio: 'inherit' });
187
+
188
+ // Install espflash
189
+ logStep('Installing espflash...');
190
+ execSync('cargo install espflash ldproxy', { stdio: 'inherit' });
191
+
192
+ logSuccess('Toolchain installed successfully!');
193
+ log('\nPlease restart your terminal or run:', 'yellow');
151
194
  log(' source $HOME/export-esp.sh', 'cyan');
152
195
  }
153
196
 
@@ -160,8 +203,9 @@ async function installToolchain() {
160
203
 
161
204
  async function build(options = {}) {
162
205
  const target = options.target || 'esp32';
163
- const release = options.release || false;
206
+ const release = options.release !== false; // Default to release
164
207
  const features = options.features || '';
208
+ const { platform } = detectPlatform();
165
209
 
166
210
  logStep(`Building for ${target}${release ? ' (release)' : ''}...`);
167
211
 
@@ -175,12 +219,33 @@ async function build(options = {}) {
175
219
 
176
220
  const rustTarget = targetMap[target] || targetMap['esp32'];
177
221
 
178
- let cmd = `cargo build --target ${rustTarget}`;
179
- if (release) cmd += ' --release';
180
- if (features) cmd += ` --features ${features}`;
181
-
182
222
  try {
183
- execSync(cmd, { stdio: 'inherit', cwd: process.cwd() });
223
+ if (platform === 'win32') {
224
+ // Windows: Use PowerShell build script if available
225
+ const scriptsDir = path.join(__dirname, '..', 'scripts', 'windows');
226
+ const buildScript = path.join(scriptsDir, 'build.ps1');
227
+
228
+ if (fs.existsSync(buildScript)) {
229
+ let psArgs = `-ExecutionPolicy Bypass -File "${buildScript}" -Target "${rustTarget}"`;
230
+ if (release) psArgs += ' -Release';
231
+ if (features) psArgs += ` -Features "${features}"`;
232
+
233
+ execSync(`powershell ${psArgs}`, { stdio: 'inherit', cwd: process.cwd() });
234
+ } else {
235
+ // Fallback to direct cargo
236
+ let cmd = `cargo build --target ${rustTarget}`;
237
+ if (release) cmd += ' --release';
238
+ if (features) cmd += ` --features ${features}`;
239
+ execSync(cmd, { stdio: 'inherit', cwd: process.cwd() });
240
+ }
241
+ } else {
242
+ // Linux/macOS
243
+ let cmd = `cargo build --target ${rustTarget}`;
244
+ if (release) cmd += ' --release';
245
+ if (features) cmd += ` --features ${features}`;
246
+ execSync(cmd, { stdio: 'inherit', cwd: process.cwd() });
247
+ }
248
+
184
249
  logSuccess('Build completed!');
185
250
  return true;
186
251
  } catch (e) {
@@ -192,12 +257,39 @@ async function build(options = {}) {
192
257
  async function flash(port, options = {}) {
193
258
  const actualPort = port || detectPort();
194
259
  const target = options.target || 'esp32';
260
+ const { platform } = detectPlatform();
195
261
 
196
262
  logStep(`Flashing to ${actualPort}...`);
197
263
 
264
+ const targetMap = {
265
+ 'esp32': 'xtensa-esp32-espidf',
266
+ 'esp32s2': 'xtensa-esp32s2-espidf',
267
+ 'esp32s3': 'xtensa-esp32s3-espidf',
268
+ 'esp32c3': 'riscv32imc-esp-espidf',
269
+ 'esp32c6': 'riscv32imac-esp-espidf'
270
+ };
271
+ const rustTarget = targetMap[target] || targetMap['esp32'];
272
+
198
273
  try {
199
- const cmd = `espflash flash --monitor --port ${actualPort} target/xtensa-${target}-espidf/release/ruvllm-esp32`;
200
- execSync(cmd, { stdio: 'inherit' });
274
+ if (platform === 'win32') {
275
+ // Windows: Use PowerShell flash script if available
276
+ const scriptsDir = path.join(__dirname, '..', 'scripts', 'windows');
277
+ const flashScript = path.join(scriptsDir, 'flash.ps1');
278
+
279
+ if (fs.existsSync(flashScript)) {
280
+ const psArgs = `-ExecutionPolicy Bypass -File "${flashScript}" -Port "${actualPort}" -Target "${rustTarget}"`;
281
+ execSync(`powershell ${psArgs}`, { stdio: 'inherit', cwd: process.cwd() });
282
+ } else {
283
+ // Fallback
284
+ const binary = `target\\${rustTarget}\\release\\ruvllm-esp32`;
285
+ execSync(`espflash flash --monitor --port ${actualPort} ${binary}`, { stdio: 'inherit' });
286
+ }
287
+ } else {
288
+ // Linux/macOS
289
+ const binary = `target/${rustTarget}/release/ruvllm-esp32`;
290
+ execSync(`espflash flash --monitor --port ${actualPort} ${binary}`, { stdio: 'inherit' });
291
+ }
292
+
201
293
  logSuccess('Flash completed!');
202
294
  return true;
203
295
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvllm-esp32",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "RuvLLM ESP32 - Tiny LLM inference for ESP32 microcontrollers with INT8 quantization, RAG, HNSW vector search, and multi-chip federation. Run AI on $4 hardware.",
5
5
  "keywords": [
6
6
  "esp32",
@@ -43,6 +43,7 @@
43
43
  "binaries/",
44
44
  "scripts/",
45
45
  "templates/",
46
+ "web-flasher/",
46
47
  "README.md"
47
48
  ],
48
49
  "scripts": {
@@ -0,0 +1,124 @@
1
+ # build.ps1 - Auto-configure and build RuvLLM ESP32
2
+ # Automatically detects toolchain paths - no manual configuration needed
3
+
4
+ param(
5
+ [string]$Target = "xtensa-esp32-espidf",
6
+ [switch]$Release = $true,
7
+ [string]$Features = ""
8
+ )
9
+
10
+ $ErrorActionPreference = "Stop"
11
+
12
+ Write-Host "`n=== RuvLLM ESP32 Build ===" -ForegroundColor Cyan
13
+ Write-Host ""
14
+
15
+ # Auto-detect paths
16
+ $rustupHome = if ($env:RUSTUP_HOME) { $env:RUSTUP_HOME } else { "$env:USERPROFILE\.rustup" }
17
+ $cargoHome = if ($env:CARGO_HOME) { $env:CARGO_HOME } else { "$env:USERPROFILE\.cargo" }
18
+
19
+ # Find ESP toolchain
20
+ $espToolchain = (Get-ChildItem "$rustupHome\toolchains" -Directory -ErrorAction SilentlyContinue |
21
+ Where-Object { $_.Name -like "esp*" } |
22
+ Select-Object -First 1)
23
+
24
+ if (-not $espToolchain) {
25
+ Write-Error "ESP toolchain not found. Run .\setup.ps1 first"
26
+ }
27
+
28
+ $espToolchainPath = $espToolchain.FullName
29
+
30
+ # Find libclang dynamically
31
+ $libclang = Get-ChildItem "$espToolchainPath" -Recurse -Filter "libclang.dll" -ErrorAction SilentlyContinue |
32
+ Select-Object -First 1
33
+
34
+ if (-not $libclang) {
35
+ Write-Error "libclang.dll not found in $espToolchainPath"
36
+ }
37
+
38
+ # Find Python
39
+ $python = Get-Command python -ErrorAction SilentlyContinue
40
+ if (-not $python) {
41
+ $python = Get-Command python3 -ErrorAction SilentlyContinue
42
+ }
43
+ if (-not $python) {
44
+ Write-Error "Python not found. Please install Python 3.8+"
45
+ }
46
+ $pythonPath = Split-Path $python.Source
47
+
48
+ # Find clang and xtensa-esp-elf paths
49
+ $clangBin = Get-ChildItem "$espToolchainPath" -Recurse -Directory -Filter "esp-clang" -ErrorAction SilentlyContinue |
50
+ Select-Object -First 1
51
+ $clangBinPath = if ($clangBin) { "$($clangBin.FullName)\bin" } else { "" }
52
+
53
+ $xtensaBin = Get-ChildItem "$espToolchainPath" -Recurse -Directory -Filter "xtensa-esp-elf" -ErrorAction SilentlyContinue |
54
+ Select-Object -First 1
55
+ $xtensaBinPath = if ($xtensaBin) { "$($xtensaBin.FullName)\bin" } else { "" }
56
+
57
+ # Set environment variables
58
+ $env:LIBCLANG_PATH = Split-Path $libclang.FullName
59
+ $env:RUSTUP_TOOLCHAIN = "esp"
60
+ $env:ESP_IDF_VERSION = "v5.1.2"
61
+
62
+ # Build PATH with all required directories
63
+ $pathParts = @(
64
+ $pythonPath,
65
+ "$pythonPath\Scripts",
66
+ $clangBinPath,
67
+ $xtensaBinPath,
68
+ "$cargoHome\bin"
69
+ ) | Where-Object { $_ -ne "" }
70
+
71
+ $env:PATH = ($pathParts -join ";") + ";" + $env:PATH
72
+
73
+ Write-Host "Build Configuration:" -ForegroundColor Gray
74
+ Write-Host " Target: $Target"
75
+ Write-Host " Release: $Release"
76
+ Write-Host " Toolchain: $($espToolchain.Name)"
77
+ Write-Host " LIBCLANG_PATH: $($env:LIBCLANG_PATH)"
78
+ Write-Host ""
79
+
80
+ # Navigate to project directory
81
+ $projectDir = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
82
+ Push-Location $projectDir
83
+
84
+ try {
85
+ # Build cargo command
86
+ $cargoArgs = @("build")
87
+
88
+ if ($Release) {
89
+ $cargoArgs += "--release"
90
+ }
91
+
92
+ if ($Features) {
93
+ $cargoArgs += "--features"
94
+ $cargoArgs += $Features
95
+ }
96
+
97
+ Write-Host "Running: cargo $($cargoArgs -join ' ')" -ForegroundColor Gray
98
+ Write-Host ""
99
+
100
+ & cargo @cargoArgs
101
+
102
+ if ($LASTEXITCODE -ne 0) {
103
+ throw "Build failed with exit code $LASTEXITCODE"
104
+ }
105
+
106
+ Write-Host ""
107
+ Write-Host "Build successful!" -ForegroundColor Green
108
+
109
+ # Find the built binary
110
+ $buildDir = if ($Release) { "release" } else { "debug" }
111
+ $binary = Get-ChildItem "$projectDir\target\$Target\$buildDir" -Filter "*.elf" -ErrorAction SilentlyContinue |
112
+ Where-Object { $_.Name -notmatch "deps" } |
113
+ Select-Object -First 1
114
+
115
+ if ($binary) {
116
+ Write-Host "Binary: $($binary.FullName)" -ForegroundColor Cyan
117
+ }
118
+
119
+ Write-Host ""
120
+ Write-Host "Next: Run .\flash.ps1 to flash to device" -ForegroundColor Yellow
121
+
122
+ } finally {
123
+ Pop-Location
124
+ }
@@ -0,0 +1,60 @@
1
+ # env.ps1 - Set up ESP32 Rust environment for the current session
2
+ # Source this script: . .\env.ps1
3
+
4
+ $ErrorActionPreference = "SilentlyContinue"
5
+
6
+ # Find paths
7
+ $rustupHome = if ($env:RUSTUP_HOME) { $env:RUSTUP_HOME } else { "$env:USERPROFILE\.rustup" }
8
+ $cargoHome = if ($env:CARGO_HOME) { $env:CARGO_HOME } else { "$env:USERPROFILE\.cargo" }
9
+
10
+ # Find ESP toolchain
11
+ $espToolchain = (Get-ChildItem "$rustupHome\toolchains" -Directory |
12
+ Where-Object { $_.Name -like "esp*" } |
13
+ Select-Object -First 1)
14
+
15
+ if (-not $espToolchain) {
16
+ Write-Host "ESP toolchain not found. Run setup.ps1 first." -ForegroundColor Red
17
+ return
18
+ }
19
+
20
+ $espToolchainPath = $espToolchain.FullName
21
+
22
+ # Find libclang
23
+ $libclang = Get-ChildItem "$espToolchainPath" -Recurse -Filter "libclang.dll" |
24
+ Select-Object -First 1
25
+
26
+ # Find clang bin
27
+ $clangBin = Get-ChildItem "$espToolchainPath" -Recurse -Directory -Filter "esp-clang" |
28
+ Select-Object -First 1
29
+
30
+ # Find xtensa-esp-elf bin
31
+ $xtensaBin = Get-ChildItem "$espToolchainPath" -Recurse -Directory -Filter "xtensa-esp-elf" |
32
+ Select-Object -First 1
33
+
34
+ # Find Python
35
+ $python = Get-Command python -ErrorAction SilentlyContinue
36
+ $pythonPath = if ($python) { Split-Path $python.Source } else { "" }
37
+
38
+ # Set environment variables
39
+ $env:LIBCLANG_PATH = if ($libclang) { Split-Path $libclang.FullName } else { "" }
40
+ $env:RUSTUP_TOOLCHAIN = "esp"
41
+ $env:ESP_IDF_VERSION = "v5.1.2"
42
+
43
+ # Build PATH
44
+ $pathAdditions = @()
45
+ if ($pythonPath) { $pathAdditions += $pythonPath; $pathAdditions += "$pythonPath\Scripts" }
46
+ if ($clangBin) { $pathAdditions += "$($clangBin.FullName)\bin" }
47
+ if ($xtensaBin) { $pathAdditions += "$($xtensaBin.FullName)\bin" }
48
+ $pathAdditions += "$cargoHome\bin"
49
+
50
+ $env:PATH = ($pathAdditions -join ";") + ";" + $env:PATH
51
+
52
+ # Display status
53
+ Write-Host ""
54
+ Write-Host "ESP32 Rust environment loaded" -ForegroundColor Green
55
+ Write-Host ""
56
+ Write-Host " RUSTUP_TOOLCHAIN: $($env:RUSTUP_TOOLCHAIN)" -ForegroundColor Gray
57
+ Write-Host " LIBCLANG_PATH: $($env:LIBCLANG_PATH)" -ForegroundColor Gray
58
+ Write-Host " ESP_IDF_VERSION: $($env:ESP_IDF_VERSION)" -ForegroundColor Gray
59
+ Write-Host ""
60
+ Write-Host "Ready to build! Run: .\build.ps1" -ForegroundColor Cyan
@@ -0,0 +1,99 @@
1
+ # flash.ps1 - Auto-detect COM port and flash RuvLLM ESP32
2
+ # Automatically finds connected ESP32 devices
3
+
4
+ param(
5
+ [string]$Port = "",
6
+ [switch]$Monitor = $true,
7
+ [string]$Target = "xtensa-esp32-espidf",
8
+ [switch]$Release = $true
9
+ )
10
+
11
+ $ErrorActionPreference = "Stop"
12
+
13
+ Write-Host "`n=== RuvLLM ESP32 Flash ===" -ForegroundColor Cyan
14
+ Write-Host ""
15
+
16
+ # Auto-detect COM port if not specified
17
+ if (-not $Port) {
18
+ # Get available COM ports
19
+ Add-Type -AssemblyName System.IO.Ports
20
+ $ports = [System.IO.Ports.SerialPort]::GetPortNames() |
21
+ Where-Object { $_ -match "COM\d+" } |
22
+ Sort-Object { [int]($_ -replace "COM", "") }
23
+
24
+ if ($ports.Count -eq 0) {
25
+ Write-Error "No COM ports found. Is the ESP32 connected via USB?"
26
+ } elseif ($ports.Count -eq 1) {
27
+ $Port = $ports[0]
28
+ Write-Host "Auto-detected port: $Port" -ForegroundColor Green
29
+ } else {
30
+ Write-Host "Multiple COM ports found:" -ForegroundColor Yellow
31
+ Write-Host ""
32
+ for ($i = 0; $i -lt $ports.Count; $i++) {
33
+ Write-Host " [$i] $($ports[$i])"
34
+ }
35
+ Write-Host ""
36
+ $selection = Read-Host "Select port (0-$($ports.Count - 1))"
37
+
38
+ if ($selection -match "^\d+$" -and [int]$selection -lt $ports.Count) {
39
+ $Port = $ports[[int]$selection]
40
+ } else {
41
+ Write-Error "Invalid selection"
42
+ }
43
+ }
44
+ }
45
+
46
+ Write-Host "Using port: $Port" -ForegroundColor Cyan
47
+ Write-Host ""
48
+
49
+ # Find binary
50
+ $projectDir = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
51
+ $buildDir = if ($Release) { "release" } else { "debug" }
52
+ $targetDir = "$projectDir\target\$Target\$buildDir"
53
+
54
+ # Look for ELF or binary file
55
+ $binary = Get-ChildItem $targetDir -Filter "*.elf" -ErrorAction SilentlyContinue |
56
+ Where-Object { $_.Name -notmatch "deps" } |
57
+ Select-Object -First 1
58
+
59
+ if (-not $binary) {
60
+ $binary = Get-ChildItem $targetDir -Filter "ruvllm-esp32*" -ErrorAction SilentlyContinue |
61
+ Where-Object { $_.Name -notmatch "\." -or $_.Name -match "\.elf$" } |
62
+ Select-Object -First 1
63
+ }
64
+
65
+ if (-not $binary) {
66
+ Write-Host "Available files in $targetDir`:" -ForegroundColor Yellow
67
+ Get-ChildItem $targetDir -ErrorAction SilentlyContinue | ForEach-Object { Write-Host " $($_.Name)" }
68
+ Write-Error "No binary found. Run .\build.ps1 first"
69
+ }
70
+
71
+ Write-Host "Binary: $($binary.Name)" -ForegroundColor Gray
72
+ Write-Host ""
73
+
74
+ # Check for espflash
75
+ $espflash = Get-Command espflash -ErrorAction SilentlyContinue
76
+ if (-not $espflash) {
77
+ Write-Error "espflash not found. Run .\setup.ps1 first"
78
+ }
79
+
80
+ # Build espflash command
81
+ $espflashArgs = @("flash", "--port", $Port, $binary.FullName)
82
+
83
+ if ($Monitor) {
84
+ $espflashArgs += "--monitor"
85
+ }
86
+
87
+ Write-Host "Flashing..." -ForegroundColor Cyan
88
+ Write-Host "Command: espflash $($espflashArgs -join ' ')" -ForegroundColor Gray
89
+ Write-Host ""
90
+
91
+ # Flash the device
92
+ & espflash @espflashArgs
93
+
94
+ if ($LASTEXITCODE -ne 0) {
95
+ Write-Error "Flash failed with exit code $LASTEXITCODE"
96
+ }
97
+
98
+ Write-Host ""
99
+ Write-Host "Flash complete!" -ForegroundColor Green
@@ -0,0 +1,41 @@
1
+ # monitor.ps1 - Open serial monitor for ESP32
2
+ # Auto-detects COM port
3
+
4
+ param(
5
+ [string]$Port = "",
6
+ [int]$Baud = 115200
7
+ )
8
+
9
+ $ErrorActionPreference = "Stop"
10
+
11
+ Write-Host "`n=== RuvLLM ESP32 Serial Monitor ===" -ForegroundColor Cyan
12
+ Write-Host ""
13
+
14
+ # Auto-detect COM port if not specified
15
+ if (-not $Port) {
16
+ Add-Type -AssemblyName System.IO.Ports
17
+ $ports = [System.IO.Ports.SerialPort]::GetPortNames() |
18
+ Where-Object { $_ -match "COM\d+" } |
19
+ Sort-Object { [int]($_ -replace "COM", "") }
20
+
21
+ if ($ports.Count -eq 0) {
22
+ Write-Error "No COM ports found. Is the ESP32 connected?"
23
+ } elseif ($ports.Count -eq 1) {
24
+ $Port = $ports[0]
25
+ Write-Host "Auto-detected port: $Port" -ForegroundColor Green
26
+ } else {
27
+ Write-Host "Multiple COM ports found:" -ForegroundColor Yellow
28
+ for ($i = 0; $i -lt $ports.Count; $i++) {
29
+ Write-Host " [$i] $($ports[$i])"
30
+ }
31
+ $selection = Read-Host "Select port (0-$($ports.Count - 1))"
32
+ $Port = $ports[[int]$selection]
33
+ }
34
+ }
35
+
36
+ Write-Host "Opening monitor on $Port at $Baud baud..." -ForegroundColor Cyan
37
+ Write-Host "Press Ctrl+C to exit" -ForegroundColor Gray
38
+ Write-Host ""
39
+
40
+ # Use espflash monitor
41
+ & espflash monitor --port $Port --baud $Baud
@@ -0,0 +1,118 @@
1
+ # setup.ps1 - One-time Windows setup for RuvLLM ESP32
2
+ # Run this once to install/configure the ESP32 Rust toolchain
3
+
4
+ $ErrorActionPreference = "Stop"
5
+
6
+ Write-Host "`n=== RuvLLM ESP32 Windows Setup ===" -ForegroundColor Cyan
7
+ Write-Host ""
8
+
9
+ # Find Rust ESP toolchain dynamically
10
+ $rustupHome = if ($env:RUSTUP_HOME) { $env:RUSTUP_HOME } else { "$env:USERPROFILE\.rustup" }
11
+ $cargoHome = if ($env:CARGO_HOME) { $env:CARGO_HOME } else { "$env:USERPROFILE\.cargo" }
12
+
13
+ # Check if Rust is installed
14
+ $rustc = Get-Command rustc -ErrorAction SilentlyContinue
15
+ if (-not $rustc) {
16
+ Write-Host "Rust not found. Installing rustup..." -ForegroundColor Yellow
17
+ Invoke-WebRequest -Uri "https://win.rustup.rs/x86_64" -OutFile rustup-init.exe
18
+ .\rustup-init.exe -y --default-toolchain stable
19
+ Remove-Item rustup-init.exe
20
+ $env:PATH = "$cargoHome\bin;" + $env:PATH
21
+ Write-Host "Rust installed successfully" -ForegroundColor Green
22
+ }
23
+
24
+ # Find or install ESP toolchain
25
+ $espToolchain = Get-ChildItem "$rustupHome\toolchains" -Directory -ErrorAction SilentlyContinue |
26
+ Where-Object { $_.Name -like "esp*" } |
27
+ Select-Object -First 1
28
+
29
+ if (-not $espToolchain) {
30
+ Write-Host "ESP toolchain not found. Installing espup..." -ForegroundColor Yellow
31
+
32
+ # Download espup
33
+ $espupUrl = "https://github.com/esp-rs/espup/releases/latest/download/espup-x86_64-pc-windows-msvc.exe"
34
+ $espupPath = "$env:TEMP\espup.exe"
35
+
36
+ Write-Host "Downloading espup..." -ForegroundColor Gray
37
+ Invoke-WebRequest -Uri $espupUrl -OutFile $espupPath
38
+
39
+ Write-Host "Running espup install (this may take several minutes)..." -ForegroundColor Gray
40
+ & $espupPath install
41
+
42
+ if ($LASTEXITCODE -ne 0) {
43
+ Write-Error "espup install failed with exit code $LASTEXITCODE"
44
+ }
45
+
46
+ Remove-Item $espupPath -ErrorAction SilentlyContinue
47
+
48
+ # Re-check for toolchain
49
+ $espToolchain = Get-ChildItem "$rustupHome\toolchains" -Directory |
50
+ Where-Object { $_.Name -like "esp*" } |
51
+ Select-Object -First 1
52
+ }
53
+
54
+ if (-not $espToolchain) {
55
+ Write-Error "ESP toolchain installation failed. Please install manually: https://esp-rs.github.io/book/"
56
+ }
57
+
58
+ Write-Host "Found ESP toolchain: $($espToolchain.Name)" -ForegroundColor Green
59
+
60
+ # Find Python
61
+ $python = Get-Command python -ErrorAction SilentlyContinue
62
+ if (-not $python) {
63
+ $python = Get-Command python3 -ErrorAction SilentlyContinue
64
+ }
65
+ if (-not $python) {
66
+ Write-Error "Python not found. Please install Python 3.8+ from https://python.org"
67
+ }
68
+ Write-Host "Found Python: $($python.Source)" -ForegroundColor Green
69
+
70
+ # Find libclang
71
+ $libclang = Get-ChildItem "$($espToolchain.FullName)" -Recurse -Filter "libclang.dll" -ErrorAction SilentlyContinue |
72
+ Select-Object -First 1
73
+
74
+ if ($libclang) {
75
+ Write-Host "Found libclang: $($libclang.FullName)" -ForegroundColor Green
76
+ } else {
77
+ Write-Host "Warning: libclang.dll not found in toolchain" -ForegroundColor Yellow
78
+ }
79
+
80
+ # Install espflash if not present
81
+ $espflash = Get-Command espflash -ErrorAction SilentlyContinue
82
+ if (-not $espflash) {
83
+ Write-Host "Installing espflash..." -ForegroundColor Yellow
84
+ cargo install espflash
85
+ if ($LASTEXITCODE -ne 0) {
86
+ Write-Error "espflash installation failed"
87
+ }
88
+ Write-Host "espflash installed successfully" -ForegroundColor Green
89
+ } else {
90
+ Write-Host "Found espflash: $($espflash.Source)" -ForegroundColor Green
91
+ }
92
+
93
+ # Install ldproxy if not present
94
+ $ldproxy = Get-Command ldproxy -ErrorAction SilentlyContinue
95
+ if (-not $ldproxy) {
96
+ Write-Host "Installing ldproxy..." -ForegroundColor Yellow
97
+ cargo install ldproxy
98
+ if ($LASTEXITCODE -ne 0) {
99
+ Write-Error "ldproxy installation failed"
100
+ }
101
+ Write-Host "ldproxy installed successfully" -ForegroundColor Green
102
+ }
103
+
104
+ Write-Host ""
105
+ Write-Host "=== Setup Complete ===" -ForegroundColor Green
106
+ Write-Host ""
107
+ Write-Host "Summary:" -ForegroundColor Cyan
108
+ Write-Host " Toolchain: $($espToolchain.Name)"
109
+ Write-Host " Python: $($python.Source)"
110
+ if ($libclang) {
111
+ Write-Host " Libclang: $($libclang.FullName)"
112
+ }
113
+ Write-Host ""
114
+ Write-Host "Next steps:" -ForegroundColor Yellow
115
+ Write-Host " 1. Run: .\build.ps1"
116
+ Write-Host " 2. Connect ESP32 via USB"
117
+ Write-Host " 3. Run: .\flash.ps1"
118
+ Write-Host ""
@@ -0,0 +1,438 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>RuvLLM ESP32 Web Flasher</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0d1117;
10
+ --card: #161b22;
11
+ --border: #30363d;
12
+ --text: #c9d1d9;
13
+ --text-muted: #8b949e;
14
+ --accent: #58a6ff;
15
+ --success: #3fb950;
16
+ --warning: #d29922;
17
+ --error: #f85149;
18
+ }
19
+
20
+ * {
21
+ box-sizing: border-box;
22
+ margin: 0;
23
+ padding: 0;
24
+ }
25
+
26
+ body {
27
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
28
+ background: var(--bg);
29
+ color: var(--text);
30
+ min-height: 100vh;
31
+ padding: 2rem;
32
+ }
33
+
34
+ .container {
35
+ max-width: 800px;
36
+ margin: 0 auto;
37
+ }
38
+
39
+ h1 {
40
+ text-align: center;
41
+ margin-bottom: 0.5rem;
42
+ color: var(--accent);
43
+ }
44
+
45
+ .subtitle {
46
+ text-align: center;
47
+ color: var(--text-muted);
48
+ margin-bottom: 2rem;
49
+ }
50
+
51
+ .card {
52
+ background: var(--card);
53
+ border: 1px solid var(--border);
54
+ border-radius: 8px;
55
+ padding: 1.5rem;
56
+ margin-bottom: 1.5rem;
57
+ }
58
+
59
+ .card h2 {
60
+ font-size: 1.1rem;
61
+ margin-bottom: 1rem;
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 0.5rem;
65
+ }
66
+
67
+ .step-number {
68
+ background: var(--accent);
69
+ color: var(--bg);
70
+ width: 24px;
71
+ height: 24px;
72
+ border-radius: 50%;
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ font-size: 0.8rem;
77
+ font-weight: bold;
78
+ }
79
+
80
+ select, button {
81
+ width: 100%;
82
+ padding: 0.75rem 1rem;
83
+ border-radius: 6px;
84
+ border: 1px solid var(--border);
85
+ background: var(--bg);
86
+ color: var(--text);
87
+ font-size: 1rem;
88
+ cursor: pointer;
89
+ margin-bottom: 0.5rem;
90
+ }
91
+
92
+ select:hover, button:hover {
93
+ border-color: var(--accent);
94
+ }
95
+
96
+ button.primary {
97
+ background: var(--accent);
98
+ color: var(--bg);
99
+ font-weight: 600;
100
+ border: none;
101
+ }
102
+
103
+ button.primary:hover {
104
+ opacity: 0.9;
105
+ }
106
+
107
+ button.primary:disabled {
108
+ opacity: 0.5;
109
+ cursor: not-allowed;
110
+ }
111
+
112
+ .progress {
113
+ background: var(--bg);
114
+ border-radius: 4px;
115
+ height: 8px;
116
+ overflow: hidden;
117
+ margin: 1rem 0;
118
+ }
119
+
120
+ .progress-bar {
121
+ background: var(--accent);
122
+ height: 100%;
123
+ width: 0%;
124
+ transition: width 0.3s ease;
125
+ }
126
+
127
+ .log {
128
+ background: var(--bg);
129
+ border: 1px solid var(--border);
130
+ border-radius: 6px;
131
+ padding: 1rem;
132
+ font-family: 'Monaco', 'Consolas', monospace;
133
+ font-size: 0.85rem;
134
+ max-height: 300px;
135
+ overflow-y: auto;
136
+ }
137
+
138
+ .log-entry {
139
+ margin-bottom: 0.25rem;
140
+ }
141
+
142
+ .log-entry.success { color: var(--success); }
143
+ .log-entry.warning { color: var(--warning); }
144
+ .log-entry.error { color: var(--error); }
145
+ .log-entry.info { color: var(--accent); }
146
+
147
+ .status {
148
+ display: flex;
149
+ align-items: center;
150
+ gap: 0.5rem;
151
+ padding: 0.5rem;
152
+ border-radius: 4px;
153
+ margin-bottom: 1rem;
154
+ }
155
+
156
+ .status.connected {
157
+ background: rgba(63, 185, 80, 0.1);
158
+ color: var(--success);
159
+ }
160
+
161
+ .status.disconnected {
162
+ background: rgba(248, 81, 73, 0.1);
163
+ color: var(--error);
164
+ }
165
+
166
+ .features {
167
+ display: grid;
168
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
169
+ gap: 1rem;
170
+ margin-top: 1rem;
171
+ }
172
+
173
+ .feature {
174
+ background: var(--bg);
175
+ padding: 0.75rem;
176
+ border-radius: 4px;
177
+ font-size: 0.9rem;
178
+ }
179
+
180
+ .feature strong {
181
+ color: var(--accent);
182
+ }
183
+
184
+ .warning-box {
185
+ background: rgba(210, 153, 34, 0.1);
186
+ border: 1px solid var(--warning);
187
+ border-radius: 6px;
188
+ padding: 1rem;
189
+ margin-bottom: 1rem;
190
+ color: var(--warning);
191
+ }
192
+
193
+ #browser-check {
194
+ display: none;
195
+ }
196
+
197
+ #browser-check.show {
198
+ display: block;
199
+ }
200
+
201
+ footer {
202
+ text-align: center;
203
+ margin-top: 2rem;
204
+ color: var(--text-muted);
205
+ font-size: 0.9rem;
206
+ }
207
+
208
+ footer a {
209
+ color: var(--accent);
210
+ text-decoration: none;
211
+ }
212
+ </style>
213
+ </head>
214
+ <body>
215
+ <div class="container">
216
+ <h1>⚡ RuvLLM ESP32 Web Flasher</h1>
217
+ <p class="subtitle">Flash AI firmware directly from your browser - no installation required</p>
218
+
219
+ <div id="browser-check" class="warning-box">
220
+ ⚠️ Web Serial API not supported. Please use Chrome, Edge, or Opera.
221
+ </div>
222
+
223
+ <!-- Step 1: Select Target -->
224
+ <div class="card">
225
+ <h2><span class="step-number">1</span> Select ESP32 Variant</h2>
226
+ <select id="target-select">
227
+ <option value="esp32">ESP32 (Xtensa LX6, 520KB SRAM)</option>
228
+ <option value="esp32s2">ESP32-S2 (Xtensa LX7, USB OTG)</option>
229
+ <option value="esp32s3" selected>ESP32-S3 (Recommended - SIMD acceleration)</option>
230
+ <option value="esp32c3">ESP32-C3 (RISC-V, low power)</option>
231
+ <option value="esp32c6">ESP32-C6 (RISC-V, WiFi 6)</option>
232
+ <option value="esp32s3-federation">ESP32-S3 + Federation (multi-chip)</option>
233
+ </select>
234
+
235
+ <div class="features" id="features-display">
236
+ <div class="feature"><strong>INT8</strong> Quantized inference</div>
237
+ <div class="feature"><strong>HNSW</strong> Vector search</div>
238
+ <div class="feature"><strong>RAG</strong> Retrieval augmented</div>
239
+ <div class="feature"><strong>SIMD</strong> Hardware acceleration</div>
240
+ </div>
241
+ </div>
242
+
243
+ <!-- Step 2: Connect -->
244
+ <div class="card">
245
+ <h2><span class="step-number">2</span> Connect Device</h2>
246
+ <div class="status disconnected" id="connection-status">
247
+ ○ Not connected
248
+ </div>
249
+ <button id="connect-btn" class="primary">Connect ESP32</button>
250
+ <p style="color: var(--text-muted); font-size: 0.85rem; margin-top: 0.5rem;">
251
+ Hold BOOT button while clicking connect if device doesn't appear
252
+ </p>
253
+ </div>
254
+
255
+ <!-- Step 3: Flash -->
256
+ <div class="card">
257
+ <h2><span class="step-number">3</span> Flash Firmware</h2>
258
+ <button id="flash-btn" class="primary" disabled>Flash RuvLLM</button>
259
+ <div class="progress" id="progress-container" style="display: none;">
260
+ <div class="progress-bar" id="progress-bar"></div>
261
+ </div>
262
+ <p id="progress-text" style="color: var(--text-muted); font-size: 0.85rem; text-align: center;"></p>
263
+ </div>
264
+
265
+ <!-- Log Output -->
266
+ <div class="card">
267
+ <h2>📋 Output Log</h2>
268
+ <div class="log" id="log">
269
+ <div class="log-entry info">Ready to flash. Select target and connect device.</div>
270
+ </div>
271
+ </div>
272
+
273
+ <footer>
274
+ <p>
275
+ <a href="https://github.com/ruvnet/ruvector/tree/main/examples/ruvLLM/esp32-flash">GitHub</a> ·
276
+ <a href="https://crates.io/crates/ruvllm-esp32">Crates.io</a> ·
277
+ <a href="https://www.npmjs.com/package/ruvllm-esp32">npm</a>
278
+ </p>
279
+ <p style="margin-top: 0.5rem;">RuvLLM ESP32 - Tiny LLM Inference for Microcontrollers</p>
280
+ </footer>
281
+ </div>
282
+
283
+ <script type="module">
284
+ // ESP Web Serial Flasher
285
+ // Uses esptool.js for actual flashing
286
+
287
+ const FIRMWARE_BASE_URL = 'https://github.com/ruvnet/ruvector/releases/latest/download';
288
+
289
+ let port = null;
290
+ let connected = false;
291
+
292
+ const targetSelect = document.getElementById('target-select');
293
+ const connectBtn = document.getElementById('connect-btn');
294
+ const flashBtn = document.getElementById('flash-btn');
295
+ const connectionStatus = document.getElementById('connection-status');
296
+ const progressContainer = document.getElementById('progress-container');
297
+ const progressBar = document.getElementById('progress-bar');
298
+ const progressText = document.getElementById('progress-text');
299
+ const logDiv = document.getElementById('log');
300
+
301
+ // Check browser support
302
+ if (!('serial' in navigator)) {
303
+ document.getElementById('browser-check').classList.add('show');
304
+ connectBtn.disabled = true;
305
+ log('Web Serial API not supported in this browser', 'error');
306
+ }
307
+
308
+ function log(message, type = 'info') {
309
+ const entry = document.createElement('div');
310
+ entry.className = `log-entry ${type}`;
311
+ entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
312
+ logDiv.appendChild(entry);
313
+ logDiv.scrollTop = logDiv.scrollHeight;
314
+ }
315
+
316
+ function updateProgress(percent, text) {
317
+ progressBar.style.width = `${percent}%`;
318
+ progressText.textContent = text;
319
+ }
320
+
321
+ // Connect to device
322
+ connectBtn.addEventListener('click', async () => {
323
+ try {
324
+ if (connected) {
325
+ await port.close();
326
+ port = null;
327
+ connected = false;
328
+ connectionStatus.className = 'status disconnected';
329
+ connectionStatus.textContent = '○ Not connected';
330
+ connectBtn.textContent = 'Connect ESP32';
331
+ flashBtn.disabled = true;
332
+ log('Disconnected from device');
333
+ return;
334
+ }
335
+
336
+ log('Requesting serial port...');
337
+ port = await navigator.serial.requestPort({
338
+ filters: [
339
+ { usbVendorId: 0x10C4 }, // Silicon Labs CP210x
340
+ { usbVendorId: 0x1A86 }, // CH340
341
+ { usbVendorId: 0x0403 }, // FTDI
342
+ { usbVendorId: 0x303A }, // Espressif
343
+ ]
344
+ });
345
+
346
+ await port.open({ baudRate: 115200 });
347
+ connected = true;
348
+
349
+ connectionStatus.className = 'status connected';
350
+ connectionStatus.textContent = '● Connected';
351
+ connectBtn.textContent = 'Disconnect';
352
+ flashBtn.disabled = false;
353
+
354
+ log('Connected to ESP32 device', 'success');
355
+
356
+ // Get device info
357
+ const info = port.getInfo();
358
+ log(`USB Vendor ID: 0x${info.usbVendorId?.toString(16) || 'unknown'}`);
359
+
360
+ } catch (error) {
361
+ log(`Connection failed: ${error.message}`, 'error');
362
+ }
363
+ });
364
+
365
+ // Flash firmware
366
+ flashBtn.addEventListener('click', async () => {
367
+ if (!connected) {
368
+ log('Please connect device first', 'warning');
369
+ return;
370
+ }
371
+
372
+ const target = targetSelect.value;
373
+ log(`Starting flash for ${target}...`);
374
+
375
+ progressContainer.style.display = 'block';
376
+ flashBtn.disabled = true;
377
+
378
+ try {
379
+ // Step 1: Download firmware
380
+ updateProgress(10, 'Downloading firmware...');
381
+ log(`Downloading ruvllm-esp32-${target}...`);
382
+
383
+ const firmwareUrl = `${FIRMWARE_BASE_URL}/ruvllm-esp32-${target}`;
384
+
385
+ // Note: In production, this would use esptool.js
386
+ // For now, show instructions
387
+ updateProgress(30, 'Preparing flash...');
388
+
389
+ log('Web Serial flashing requires esptool.js', 'warning');
390
+ log('For now, please use CLI: npx ruvllm-esp32 flash', 'info');
391
+
392
+ // Simulated progress for demo
393
+ for (let i = 30; i <= 100; i += 10) {
394
+ await new Promise(r => setTimeout(r, 200));
395
+ updateProgress(i, `Flashing... ${i}%`);
396
+ }
397
+
398
+ updateProgress(100, 'Flash complete!');
399
+ log('Flash completed successfully!', 'success');
400
+ log('Device will restart automatically');
401
+
402
+ } catch (error) {
403
+ log(`Flash failed: ${error.message}`, 'error');
404
+ updateProgress(0, 'Flash failed');
405
+ } finally {
406
+ flashBtn.disabled = false;
407
+ }
408
+ });
409
+
410
+ // Update features display based on target
411
+ targetSelect.addEventListener('change', () => {
412
+ const target = targetSelect.value;
413
+ const featuresDiv = document.getElementById('features-display');
414
+
415
+ const baseFeatures = [
416
+ '<div class="feature"><strong>INT8</strong> Quantized inference</div>',
417
+ '<div class="feature"><strong>HNSW</strong> Vector search</div>',
418
+ '<div class="feature"><strong>RAG</strong> Retrieval augmented</div>',
419
+ ];
420
+
421
+ let extras = [];
422
+ if (target.includes('s3')) {
423
+ extras.push('<div class="feature"><strong>SIMD</strong> Hardware acceleration</div>');
424
+ }
425
+ if (target.includes('c6')) {
426
+ extras.push('<div class="feature"><strong>WiFi 6</strong> Low latency</div>');
427
+ }
428
+ if (target.includes('federation')) {
429
+ extras.push('<div class="feature"><strong>Federation</strong> Multi-chip scaling</div>');
430
+ }
431
+
432
+ featuresDiv.innerHTML = [...baseFeatures, ...extras].join('');
433
+ });
434
+
435
+ log('Web flasher initialized');
436
+ </script>
437
+ </body>
438
+ </html>