OTA for frontend works. Created documentation to know how to do it, upload and voila.
This commit is contained in:
@@ -62,3 +62,21 @@ Because `CONFIG_CALENDINK_DEPLOY_WEB_PAGES` is enabled, CMake will automatically
|
|||||||
1. Detect your `frontend/dist/` folder.
|
1. Detect your `frontend/dist/` folder.
|
||||||
2. Run `mklittlefs` to package it into `www.bin`.
|
2. Run `mklittlefs` to package it into `www.bin`.
|
||||||
3. Flash `www.bin` directly to the active `www_0` partition on the ESP32!
|
3. Flash `www.bin` directly to the active `www_0` partition on the ESP32!
|
||||||
|
|
||||||
|
## 5. Over-The-Air (OTA) Updates
|
||||||
|
|
||||||
|
Once the backend supports it (Phase 2+), you can update the frontend without using USB or `idf.py`.
|
||||||
|
|
||||||
|
1. **Build the assets**: `npm run build:esp32`
|
||||||
|
2. **Package the image**: `npm run ota:package`
|
||||||
|
- This generates a `frontend/bin/www.bin` file.
|
||||||
|
- **Configuration**: If the tool is not in your PATH, add its path to `frontend/.env`:
|
||||||
|
```env
|
||||||
|
MKLITTLEFS_PATH=C:\path\to\mklittlefs.exe
|
||||||
|
```
|
||||||
|
*(Note: The script also supports `littlefs-python.exe` usually found in the `build/littlefs_py_venv/Scripts/` folder).*
|
||||||
|
3. **Upload via Dashboard**:
|
||||||
|
- Open the dashboard in your browser.
|
||||||
|
- Go to the **Frontend Update** section.
|
||||||
|
- Select the `www.bin` file and click **Flash**.
|
||||||
|
- The device will automatically write to the inactive partition and reboot.
|
||||||
|
|||||||
2
Provider/frontend/.env
Normal file
2
Provider/frontend/.env
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_API_BASE=http://192.168.50.216
|
||||||
|
MKLITTLEFS_PATH=W:\Classified\Calendink\Provider\build\littlefs_py_venv\Scripts\littlefs-python.exe
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"build:esp32": "vite build && node scripts/gzip.js",
|
"build:esp32": "vite build && node scripts/gzip.js",
|
||||||
|
"ota:package": "node scripts/package.js",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
113
Provider/frontend/scripts/package.js
Normal file
113
Provider/frontend/scripts/package.js
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* OTA Packaging Script
|
||||||
|
* Generates www.bin from dist/ using mklittlefs or littlefs-python.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { execSync } from 'child_process';
|
||||||
|
import { resolve, dirname } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { existsSync, readFileSync, mkdirSync } from 'fs';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const projectRoot = resolve(__dirname, '..');
|
||||||
|
const distDir = resolve(projectRoot, 'dist');
|
||||||
|
const binDir = resolve(projectRoot, 'bin');
|
||||||
|
const outputFile = resolve(binDir, 'www.bin');
|
||||||
|
|
||||||
|
// Ensure bin directory exists
|
||||||
|
if (!existsSync(binDir)) {
|
||||||
|
mkdirSync(binDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration matching partitions.csv (1MB = 1048576 bytes)
|
||||||
|
const FS_SIZE = 1048576;
|
||||||
|
const BLOCK_SIZE = 4096;
|
||||||
|
const PAGE_SIZE = 256;
|
||||||
|
|
||||||
|
console.log('--- OTA Packaging ---');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple .env parser to load MKLITTLEFS_PATH without external dependencies
|
||||||
|
*/
|
||||||
|
function loadEnv() {
|
||||||
|
const envPaths = [
|
||||||
|
resolve(projectRoot, '.env.local'),
|
||||||
|
resolve(projectRoot, '.env')
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const path of envPaths) {
|
||||||
|
if (existsSync(path)) {
|
||||||
|
console.log(`Loading config from: ${path}`);
|
||||||
|
const content = readFileSync(path, 'utf8');
|
||||||
|
content.split('\n').forEach(line => {
|
||||||
|
const [key, ...valueParts] = line.split('=');
|
||||||
|
if (key && valueParts.length > 0) {
|
||||||
|
const value = valueParts.join('=').trim().replace(/^["']|["']$/g, '');
|
||||||
|
process.env[key.trim()] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadEnv();
|
||||||
|
|
||||||
|
if (!existsSync(distDir)) {
|
||||||
|
console.error('Error: dist/ directory not found. Run "npm run build:esp32" first.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find mklittlefs or littlefs-python
|
||||||
|
const findTool = () => {
|
||||||
|
// 1. Check environment variable (from manual set or .env)
|
||||||
|
if (process.env.MKLITTLEFS_PATH) {
|
||||||
|
if (existsSync(process.env.MKLITTLEFS_PATH)) {
|
||||||
|
return process.env.MKLITTLEFS_PATH;
|
||||||
|
}
|
||||||
|
console.warn(`Warning: MKLITTLEFS_PATH set to ${process.env.MKLITTLEFS_PATH} but file not found.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check system PATH
|
||||||
|
const tools = ['mklittlefs', 'littlefs-python'];
|
||||||
|
for (const tool of tools) {
|
||||||
|
try {
|
||||||
|
execSync(`${tool} --version`, { stdio: 'ignore' });
|
||||||
|
return tool;
|
||||||
|
} catch (e) {
|
||||||
|
// Not in path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tool = findTool();
|
||||||
|
|
||||||
|
if (!tool) {
|
||||||
|
console.error('Error: No LittleFS tool found (checked mklittlefs and littlefs-python).');
|
||||||
|
console.info('Please set MKLITTLEFS_PATH in your .env file.');
|
||||||
|
console.info('Example: MKLITTLEFS_PATH=C:\\Espressif\\tools\\mklittlefs\\v3.2.0\\mklittlefs.exe');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`Using tool: ${tool}`);
|
||||||
|
console.log(`Packaging ${distDir} -> ${outputFile}...`);
|
||||||
|
|
||||||
|
let cmd;
|
||||||
|
// Check if it is the Python version or the C++ version
|
||||||
|
if (tool.includes('littlefs-python')) {
|
||||||
|
// Python style: littlefs-python create <dir> <output> --fs-size=<size> --block-size=<block>
|
||||||
|
cmd = `"${tool}" create "${distDir}" "${outputFile}" --fs-size=${FS_SIZE} --block-size=${BLOCK_SIZE}`;
|
||||||
|
} else {
|
||||||
|
// C++ style: mklittlefs -c <dir> -s <size> -b <block> -p <page> <output>
|
||||||
|
cmd = `"${tool}" -c "${distDir}" -s ${FS_SIZE} -b ${BLOCK_SIZE} -p ${PAGE_SIZE} "${outputFile}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Running: ${cmd}`);
|
||||||
|
execSync(cmd, { stdio: 'inherit' });
|
||||||
|
console.log('Success: www.bin created.');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error during packaging:', e.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import { getOTAStatus, uploadOTAFrontend } from "./api.js";
|
import { getOTAStatus, uploadOTAFrontend } from "./api.js";
|
||||||
|
|
||||||
|
const IS_DEV = import.meta.env.DEV;
|
||||||
|
|
||||||
/** @type {'idle' | 'loading_status' | 'uploading' | 'success' | 'error'} */
|
/** @type {'idle' | 'loading_status' | 'uploading' | 'success' | 'error'} */
|
||||||
let status = $state("idle");
|
let status = $state("idle");
|
||||||
let errorMsg = $state("");
|
let errorMsg = $state("");
|
||||||
@@ -67,6 +69,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if !IS_DEV}
|
||||||
<div class="bg-bg-card border border-border rounded-xl p-5 mt-4">
|
<div class="bg-bg-card border border-border rounded-xl p-5 mt-4">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h2 class="text-sm font-semibold text-text-primary">
|
<h2 class="text-sm font-semibold text-text-primary">
|
||||||
@@ -147,4 +150,5 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user