WIP: service and broken partitions
This commit is contained in:
23
cmds.sh
Normal file
23
cmds.sh
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# -> file not found
|
||||||
|
|
||||||
|
mklittlefs -c data \
|
||||||
|
-s 0x9000 \
|
||||||
|
-b 4096 \
|
||||||
|
-p 256 \
|
||||||
|
littlefs.bin
|
||||||
|
|
||||||
|
|
||||||
|
esptool.py --port /dev/ttyUSB0 \
|
||||||
|
--baud 115200 \
|
||||||
|
write_flash 0x71000 littlefs.bin
|
||||||
|
|
||||||
|
# pio -->
|
||||||
|
|
||||||
|
"mklittlefs" -c data -p 256 -b 4096 -s 262144 .pio/build/esp01_1m/littlefs.bin
|
||||||
|
|
||||||
|
esptool.py --before no_reset \
|
||||||
|
--after soft_reset \
|
||||||
|
--chip esp8266 \
|
||||||
|
--port "/dev/ttyUSB0" \
|
||||||
|
--baud 115200 \
|
||||||
|
write_flash 765952 .pio/build/esp01_1m/littlefs.bin
|
||||||
File diff suppressed because it is too large
Load Diff
19
ctl.sh
19
ctl.sh
@@ -27,6 +27,25 @@ function flash {
|
|||||||
${@:-info}
|
${@:-info}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mkfs {
|
||||||
|
~/bin/mklittlefs -c data \
|
||||||
|
-s 0x9000 \
|
||||||
|
-b 4096 \
|
||||||
|
-p 256 \
|
||||||
|
littlefs.bin
|
||||||
|
}
|
||||||
|
|
||||||
|
function flashfs {
|
||||||
|
esptool.py --port /dev/ttyUSB0 \
|
||||||
|
--baud 115200 \
|
||||||
|
write_flash 0xbb000 littlefs.bin
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadfs {
|
||||||
|
echo "Uploading files to LittleFS..."
|
||||||
|
pio run -e $1 -t uploadfs
|
||||||
|
}
|
||||||
|
|
||||||
function ota {
|
function ota {
|
||||||
function update {
|
function update {
|
||||||
echo "Updating node at $1 with $2... "
|
echo "Updating node at $1 with $2... "
|
||||||
|
|||||||
165
data/index.html
Normal file
165
data/index.html
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Spore 🍄 hehehehe </title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
font-size: 2.5em;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
.status-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
.status-card {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
.status-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
.status-value {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
.api-section {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
.api-links {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.api-link {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 25px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
.api-link:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-color: rgba(255, 255, 255, 0.5);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>🍄 Spore Node</h1>
|
||||||
|
|
||||||
|
<div class="status-grid">
|
||||||
|
<div class="status-card">
|
||||||
|
<h3>Node Status</h3>
|
||||||
|
<div class="status-value" id="nodeStatus">Loading...</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-card">
|
||||||
|
<h3>Network</h3>
|
||||||
|
<div class="status-value" id="networkStatus">Loading...</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-card">
|
||||||
|
<h3>Tasks</h3>
|
||||||
|
<div class="status-value" id="taskStatus">Loading...</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-card">
|
||||||
|
<h3>Cluster</h3>
|
||||||
|
<div class="status-value" id="clusterStatus">Loading...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="api-section">
|
||||||
|
<h2>API Endpoints</h2>
|
||||||
|
<div class="api-links">
|
||||||
|
<a href="/api/node/status" class="api-link">Node Status</a>
|
||||||
|
<a href="/api/network/status" class="api-link">Network Status</a>
|
||||||
|
<a href="/api/tasks/status" class="api-link">Tasks Status</a>
|
||||||
|
<a href="/api/cluster/members" class="api-link">Cluster Members</a>
|
||||||
|
<a href="/api/node/endpoints" class="api-link">All Endpoints</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Load initial data
|
||||||
|
async function loadStatus() {
|
||||||
|
try {
|
||||||
|
const [nodeResponse, networkResponse, taskResponse, clusterResponse] = await Promise.all([
|
||||||
|
fetch('/api/node/status').then(r => r.json()).catch(() => null),
|
||||||
|
fetch('/api/network/status').then(r => r.json()).catch(() => null),
|
||||||
|
fetch('/api/tasks/status').then(r => r.json()).catch(() => null),
|
||||||
|
fetch('/api/cluster/members').then(r => r.json()).catch(() => null)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update node status
|
||||||
|
if (nodeResponse) {
|
||||||
|
document.getElementById('nodeStatus').textContent =
|
||||||
|
nodeResponse.uptime ? `${Math.floor(nodeResponse.uptime / 1000)}s` : 'Online';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update network status
|
||||||
|
if (networkResponse) {
|
||||||
|
document.getElementById('networkStatus').textContent =
|
||||||
|
networkResponse.connected ? 'Connected' : 'Disconnected';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update task status
|
||||||
|
if (taskResponse) {
|
||||||
|
const activeTasks = taskResponse.tasks ?
|
||||||
|
taskResponse.tasks.filter(t => t.status === 'running').length : 0;
|
||||||
|
document.getElementById('taskStatus').textContent = `${activeTasks} active`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cluster status
|
||||||
|
if (clusterResponse) {
|
||||||
|
const memberCount = clusterResponse.members ?
|
||||||
|
Object.keys(clusterResponse.members).length : 0;
|
||||||
|
document.getElementById('clusterStatus').textContent = `${memberCount} nodes`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading status:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load status on page load
|
||||||
|
loadStatus();
|
||||||
|
|
||||||
|
// Refresh status every 5 seconds
|
||||||
|
setInterval(loadStatus, 5000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
82
examples/static_web/README.md
Normal file
82
examples/static_web/README.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Static Web Server Example
|
||||||
|
|
||||||
|
This example demonstrates how to serve static HTML files from LittleFS using the Spore framework.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Serves static files from LittleFS filesystem
|
||||||
|
- Beautiful web interface with real-time status updates
|
||||||
|
- Responsive design that works on mobile and desktop
|
||||||
|
- Automatic API endpoint discovery and status monitoring
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `main.cpp` - Main application entry point
|
||||||
|
- `data/index.html` - Web interface (served from LittleFS)
|
||||||
|
|
||||||
|
## Web Interface
|
||||||
|
|
||||||
|
The web interface provides:
|
||||||
|
|
||||||
|
- **Node Status** - Shows uptime and system information
|
||||||
|
- **Network Status** - Displays WiFi connection status
|
||||||
|
- **Task Status** - Shows active background tasks
|
||||||
|
- **Cluster Status** - Displays cluster membership
|
||||||
|
- **API Links** - Quick access to all available API endpoints
|
||||||
|
|
||||||
|
## Building and Flashing
|
||||||
|
|
||||||
|
### Build for D1 Mini (recommended for web interface)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build the firmware
|
||||||
|
pio run -e d1_mini_static_web
|
||||||
|
|
||||||
|
# Flash to device
|
||||||
|
pio run -e d1_mini_static_web -t upload
|
||||||
|
```
|
||||||
|
|
||||||
|
### Upload Files to LittleFS
|
||||||
|
|
||||||
|
After flashing the firmware, you need to upload the HTML files to the LittleFS filesystem:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Upload data directory to LittleFS
|
||||||
|
pio run -e d1_mini_static_web -t uploadfs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Flash the firmware to your ESP8266 device
|
||||||
|
2. Upload the data files to LittleFS
|
||||||
|
3. Connect to the device's WiFi network
|
||||||
|
4. Open a web browser and navigate to `http://<device-ip>/`
|
||||||
|
5. The web interface will load and display real-time status information
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
The web interface automatically discovers and displays links to all available API endpoints:
|
||||||
|
|
||||||
|
- `/api/node/status` - Node status and system information
|
||||||
|
- `/api/network/status` - Network and WiFi status
|
||||||
|
- `/api/tasks/status` - Task management status
|
||||||
|
- `/api/cluster/members` - Cluster membership
|
||||||
|
- `/api/node/endpoints` - Complete API endpoint list
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
To customize the web interface:
|
||||||
|
|
||||||
|
1. Modify `data/index.html` with your desired content
|
||||||
|
2. Add additional static files (CSS, JS, images) to the `data/` directory
|
||||||
|
3. Re-upload the files using `pio run -e d1_mini_static_web -t uploadfs`
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
data/
|
||||||
|
├── index.html # Main web interface
|
||||||
|
└── (other static files) # Additional web assets
|
||||||
|
```
|
||||||
|
|
||||||
|
The StaticFileService automatically serves files from the LittleFS root directory, so any files placed in the `data/` directory will be accessible via HTTP.
|
||||||
21
examples/static_web/main.cpp
Normal file
21
examples/static_web/main.cpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include "spore/Spore.h"
|
||||||
|
|
||||||
|
// Create Spore instance
|
||||||
|
Spore spore;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
// Initialize Spore framework
|
||||||
|
spore.setup();
|
||||||
|
|
||||||
|
// Start the framework (this will start the API server with static file serving)
|
||||||
|
spore.begin();
|
||||||
|
|
||||||
|
Serial.println("Static web server started!");
|
||||||
|
Serial.println("Visit http://<node-ip>/ to see the web interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// Let Spore handle the main loop
|
||||||
|
spore.loop();
|
||||||
|
}
|
||||||
24
include/spore/services/StaticFileService.h
Normal file
24
include/spore/services/StaticFileService.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "spore/Service.h"
|
||||||
|
#include "spore/core/ApiServer.h"
|
||||||
|
#include "spore/core/NodeContext.h"
|
||||||
|
#include <FS.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
|
||||||
|
class StaticFileService : public Service {
|
||||||
|
public:
|
||||||
|
StaticFileService(NodeContext& ctx, ApiServer& apiServer);
|
||||||
|
void registerEndpoints(ApiServer& api) override;
|
||||||
|
const char* getName() const override { return name.c_str(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const String name;
|
||||||
|
NodeContext& ctx;
|
||||||
|
ApiServer& apiServer;
|
||||||
|
|
||||||
|
void handleRootRequest(AsyncWebServerRequest* request);
|
||||||
|
void handleStaticFileRequest(AsyncWebServerRequest* request);
|
||||||
|
void handleNotFound(AsyncWebServerRequest* request);
|
||||||
|
String getContentType(const String& filename);
|
||||||
|
bool fileExists(const String& path);
|
||||||
|
};
|
||||||
BIN
littlefs.bin
Normal file
BIN
littlefs.bin
Normal file
Binary file not shown.
6
partitions_lfs.csv
Normal file
6
partitions_lfs.csv
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Name, Type, Subtype, Offset, Size, Flags
|
||||||
|
nvs, data, nvs, 0x9000, 0x6000,
|
||||||
|
otadata, data, ota, 0xf000, 0x2000,
|
||||||
|
app0, app, factory, 0x11000, 0x30000, # 192 KB
|
||||||
|
app1, app, ota_0, 0x41000, 0x30000, # 192 KB
|
||||||
|
littlefs, data, littlefs, 0x71000, 0x9000 # 36 KB
|
||||||
|
@@ -3,4 +3,4 @@ nvs, data, nvs, , 0x4000,
|
|||||||
otadata, data, ota, , 0x2000,
|
otadata, data, ota, , 0x2000,
|
||||||
app0, app, ota_0, , 0x3E000,
|
app0, app, ota_0, , 0x3E000,
|
||||||
app1, app, ota_1, , 0x3E000,
|
app1, app, ota_1, , 0x3E000,
|
||||||
spiffs, data, spiffs, , 0x1C000,
|
littlefs, data, littlefs, , 0x1C000,
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
; https://docs.platformio.org/page/projectconf.html
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
[platformio]
|
[platformio]
|
||||||
;default_envs = esp01_1m
|
default_envs = esp01_1m
|
||||||
src_dir = .
|
src_dir = .
|
||||||
|
|
||||||
[common]
|
[common]
|
||||||
@@ -24,9 +24,16 @@ board = esp01_1m
|
|||||||
framework = arduino
|
framework = arduino
|
||||||
upload_speed = 115200
|
upload_speed = 115200
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
board_build.partitions = partitions_ota_1M.csv
|
board_build.f_cpu = 80000000L
|
||||||
board_build.flash_mode = dout ; ESP‑01S uses DOUT on 1 Mbit flash
|
board_build.flash_mode = qio
|
||||||
board_build.flash_size = 1M
|
board_build.filesystem = littlefs
|
||||||
|
;board_build.ldscript = eagle.flash.1m256.ld
|
||||||
|
;board_build.partitions = partitions_ota_1M.csv
|
||||||
|
;board_build.partitions = partitions_lfs.csv
|
||||||
|
#board_build.filesystem_image_size = 0x9000
|
||||||
|
|
||||||
|
#build_flags = -DESP8266_PARTITION_TABLE_OFFSET=0x8000
|
||||||
|
# -DESP8266_PARTITION_TABLE_FILE=\"partitions_lfs.csv\"
|
||||||
lib_deps = ${common.lib_deps}
|
lib_deps = ${common.lib_deps}
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
+<examples/base/*.cpp>
|
+<examples/base/*.cpp>
|
||||||
@@ -145,3 +152,21 @@ build_src_filter =
|
|||||||
+<src/spore/services/*.cpp>
|
+<src/spore/services/*.cpp>
|
||||||
+<src/spore/types/*.cpp>
|
+<src/spore/types/*.cpp>
|
||||||
+<src/internal/*.cpp>
|
+<src/internal/*.cpp>
|
||||||
|
|
||||||
|
[env:d1_mini_static_web]
|
||||||
|
platform = platformio/espressif8266@^4.2.1
|
||||||
|
board = d1_mini
|
||||||
|
framework = arduino
|
||||||
|
upload_speed = 115200
|
||||||
|
monitor_speed = 115200
|
||||||
|
board_build.flash_mode = dio
|
||||||
|
board_build.flash_size = 4M
|
||||||
|
board_build.filesystem = littlefs
|
||||||
|
lib_deps = ${common.lib_deps}
|
||||||
|
build_src_filter =
|
||||||
|
+<examples/static_web/*.cpp>
|
||||||
|
+<src/spore/*.cpp>
|
||||||
|
+<src/spore/core/*.cpp>
|
||||||
|
+<src/spore/services/*.cpp>
|
||||||
|
+<src/spore/types/*.cpp>
|
||||||
|
+<src/internal/*.cpp>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "spore/services/NetworkService.h"
|
#include "spore/services/NetworkService.h"
|
||||||
#include "spore/services/ClusterService.h"
|
#include "spore/services/ClusterService.h"
|
||||||
#include "spore/services/TaskService.h"
|
#include "spore/services/TaskService.h"
|
||||||
|
#include "spore/services/StaticFileService.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
Spore::Spore() : ctx(), network(ctx), taskManager(ctx), cluster(ctx, taskManager),
|
Spore::Spore() : ctx(), network(ctx), taskManager(ctx), cluster(ctx, taskManager),
|
||||||
@@ -119,12 +120,14 @@ void Spore::registerCoreServices() {
|
|||||||
auto networkService = std::make_shared<NetworkService>(network);
|
auto networkService = std::make_shared<NetworkService>(network);
|
||||||
auto clusterService = std::make_shared<ClusterService>(ctx);
|
auto clusterService = std::make_shared<ClusterService>(ctx);
|
||||||
auto taskService = std::make_shared<TaskService>(taskManager);
|
auto taskService = std::make_shared<TaskService>(taskManager);
|
||||||
|
auto staticFileService = std::make_shared<StaticFileService>(ctx, apiServer);
|
||||||
|
|
||||||
// Add to services list
|
// Add to services list
|
||||||
services.push_back(nodeService);
|
services.push_back(nodeService);
|
||||||
services.push_back(networkService);
|
services.push_back(networkService);
|
||||||
services.push_back(clusterService);
|
services.push_back(clusterService);
|
||||||
services.push_back(taskService);
|
services.push_back(taskService);
|
||||||
|
services.push_back(staticFileService);
|
||||||
|
|
||||||
Serial.println("[Spore] Core services registered");
|
Serial.println("[Spore] Core services registered");
|
||||||
}
|
}
|
||||||
|
|||||||
109
src/spore/services/StaticFileService.cpp
Normal file
109
src/spore/services/StaticFileService.cpp
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#include "spore/services/StaticFileService.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
const String StaticFileService::name = "StaticFileService";
|
||||||
|
|
||||||
|
StaticFileService::StaticFileService(NodeContext& ctx, ApiServer& apiServer)
|
||||||
|
: ctx(ctx), apiServer(apiServer) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void StaticFileService::registerEndpoints(ApiServer& api) {
|
||||||
|
// Initialize LittleFS
|
||||||
|
if (!LittleFS.begin()) {
|
||||||
|
Serial.println("[StaticFileService] LittleFS Mount Failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Serial.println("[StaticFileService] LittleFS mounted successfully");
|
||||||
|
|
||||||
|
// Root endpoint - serve index.html
|
||||||
|
api.addEndpoint("/", HTTP_GET,
|
||||||
|
[this](AsyncWebServerRequest* request) { handleRootRequest(request); },
|
||||||
|
std::vector<ParamSpec>{});
|
||||||
|
|
||||||
|
// Static file serving for any path
|
||||||
|
api.addEndpoint("/*", HTTP_GET,
|
||||||
|
[this](AsyncWebServerRequest* request) { handleStaticFileRequest(request); },
|
||||||
|
std::vector<ParamSpec>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
void StaticFileService::handleRootRequest(AsyncWebServerRequest* request) {
|
||||||
|
// Serve index.html from root
|
||||||
|
String path = "/index.html";
|
||||||
|
|
||||||
|
if (!fileExists(path)) {
|
||||||
|
request->send(404, "text/plain", "File not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File file = LittleFS.open(path, "r");
|
||||||
|
if (!file) {
|
||||||
|
request->send(500, "text/plain", "Failed to open file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String contentType = getContentType(path);
|
||||||
|
request->send(LittleFS, path, contentType);
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StaticFileService::handleStaticFileRequest(AsyncWebServerRequest* request) {
|
||||||
|
String path = request->url();
|
||||||
|
|
||||||
|
// Remove leading slash for LittleFS path
|
||||||
|
if (path.startsWith("/")) {
|
||||||
|
path = path.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If path is empty or just "/", serve index.html
|
||||||
|
if (path.isEmpty() || path == "/") {
|
||||||
|
path = "index.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if file exists
|
||||||
|
if (!fileExists("/" + path)) {
|
||||||
|
request->send(404, "text/plain", "File not found: " + path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String contentType = getContentType(path);
|
||||||
|
request->send(LittleFS, "/" + path, contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StaticFileService::handleNotFound(AsyncWebServerRequest* request) {
|
||||||
|
// Try to serve index.html as fallback
|
||||||
|
if (fileExists("/index.html")) {
|
||||||
|
request->send(LittleFS, "/index.html", "text/html");
|
||||||
|
} else {
|
||||||
|
request->send(404, "text/plain", "Not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String StaticFileService::getContentType(const String& filename) {
|
||||||
|
if (filename.endsWith(".html") || filename.endsWith(".htm")) {
|
||||||
|
return "text/html";
|
||||||
|
} else if (filename.endsWith(".css")) {
|
||||||
|
return "text/css";
|
||||||
|
} else if (filename.endsWith(".js")) {
|
||||||
|
return "application/javascript";
|
||||||
|
} else if (filename.endsWith(".json")) {
|
||||||
|
return "application/json";
|
||||||
|
} else if (filename.endsWith(".png")) {
|
||||||
|
return "image/png";
|
||||||
|
} else if (filename.endsWith(".jpg") || filename.endsWith(".jpeg")) {
|
||||||
|
return "image/jpeg";
|
||||||
|
} else if (filename.endsWith(".gif")) {
|
||||||
|
return "image/gif";
|
||||||
|
} else if (filename.endsWith(".svg")) {
|
||||||
|
return "image/svg+xml";
|
||||||
|
} else if (filename.endsWith(".ico")) {
|
||||||
|
return "image/x-icon";
|
||||||
|
} else if (filename.endsWith(".txt")) {
|
||||||
|
return "text/plain";
|
||||||
|
} else {
|
||||||
|
return "application/octet-stream";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StaticFileService::fileExists(const String& path) {
|
||||||
|
return LittleFS.exists(path);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user