Initialisation depot

This commit is contained in:
Serge NOEL
2026-02-10 12:12:11 +01:00
commit c3176e8d79
818 changed files with 52573 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
# Web Interface Files Setup
This directory contains the web interface files that will be uploaded to the ESP32's LittleFS filesystem.
## Structure
```
data/
├── index.html # Main HTML page
├── css/
│ ├── bootstrap.min.css # Bootstrap CSS (local copy)
│ └── style.css # Custom styles
└── js/
├── bootstrap.bundle.min.js # Bootstrap JS (local copy)
└── app.js # Application JavaScript
```
## How to Upload Files to ESP32
### Method 1: Using PlatformIO (Recommended)
1. **Install LittleFS Uploader**:
- In VS Code, open PlatformIO Home
- Go to Platforms → Espressif 32
- Make sure it's installed/updated
2. **Download Bootstrap Files** (if not already present):
Download these files and place them in the appropriate directories:
- **Bootstrap CSS** (v5.3.0):
- URL: https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css
- Save to: `data/css/bootstrap.min.css`
- **Bootstrap JS** (v5.3.0):
- URL: https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js
- Save to: `data/js/bootstrap.bundle.min.js`
3. **Upload Filesystem**:
- In VS Code, click PlatformIO icon
- Under PROJECT TASKS → env:wemos_d1_mini32
- Click "Upload Filesystem Image"
- Wait for upload to complete
### Method 2: Manual Download and Upload
If you need to download Bootstrap files manually:
```bash
# Navigate to data directories
cd data/css
wget https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css
cd ../js
wget https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js
```
Or use curl:
```bash
cd data/css
curl -o bootstrap.min.css https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css
cd ../js
curl -o bootstrap.bundle.min.js https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js
```
### Verifying Upload
After uploading, you can verify the files are present by:
1. Opening Serial Monitor (115200 baud)
2. Look for "LittleFS mounted successfully" message
3. Access the web interface and check browser console for any 404 errors
## File Sizes (Approximate)
- `bootstrap.min.css`: ~190 KB
- `bootstrap.bundle.min.js`: ~220 KB
- `style.css`: ~1 KB
- `app.js`: ~4 KB
- `index.html`: ~4 KB
**Total**: ~420 KB (ESP32 has 1.5MB+ available for LittleFS)
## Benefits of LittleFS Approach
**Better Organization**: Separate HTML, CSS, and JS files
**Offline Operation**: Works without internet connection
**Easier Maintenance**: Edit files without recompiling firmware
**Faster Updates**: Only upload filesystem when web files change
**Better Performance**: No need to parse embedded strings
**Standard Development**: Use familiar web development workflow
## Troubleshooting
### LittleFS Mount Failed
- Ensure filesystem is properly formatted
- Try uploading filesystem image again
- Check serial output for detailed error messages
### 404 Errors on Static Files
- Verify files are in correct directories
- Check file names match exactly (case-sensitive)
- Re-upload filesystem image
### Bootstrap Not Loading
- Download Bootstrap files to `data/css/` and `data/js/`
- Upload filesystem image
- Clear browser cache and reload

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,44 @@
body {
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 800px;
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0,0,0,0.1);
}
.status-indicator {
width: 20px;
height: 20px;
border-radius: 50%;
display: inline-block;
margin-right: 10px;
}
.status-connected {
background-color: #28a745;
}
.status-disconnected {
background-color: #dc3545;
}
.speed-value {
font-size: 2em;
font-weight: bold;
text-align: center;
margin: 20px 0;
}
.function-btn {
margin: 5px;
}
.direction-indicator {
font-size: 1.2em;
margin-left: 10px;
}

View File

@@ -0,0 +1,27 @@
#!/bin/bash
# Script to download Bootstrap files for offline use
echo "Downloading Bootstrap 5.3.0 files..."
# Create directories if they don't exist
mkdir -p css
mkdir -p js
# Download Bootstrap CSS
echo "Downloading Bootstrap CSS..."
curl -L -o css/bootstrap.min.css https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css
# Download Bootstrap JS Bundle (includes Popper)
echo "Downloading Bootstrap JS Bundle..."
curl -L -o js/bootstrap.bundle.min.js https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js
echo ""
echo "Download complete!"
echo ""
echo "Files downloaded:"
echo " - css/bootstrap.min.css ($(du -h css/bootstrap.min.css | cut -f1))"
echo " - js/bootstrap.bundle.min.js ($(du -h js/bootstrap.bundle.min.js | cut -f1))"
echo ""
echo "Now you can upload the filesystem to your ESP32:"
echo " 1. In VS Code, open PlatformIO"
echo " 2. Click 'Upload Filesystem Image' under PROJECT TASKS"

View File

@@ -0,0 +1,124 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Locomotive Test Bench</title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/style.css" rel="stylesheet">
</head>
<body>
<div class="container">
<h1 class="text-center mb-4">🚂 Locomotive Test Bench</h1>
<!-- Status Section -->
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Status</h5>
<p><span class="status-indicator" id="statusIndicator"></span>
<span id="statusText">Connecting...</span></p>
<p class="mb-0"><small>IP: <span id="ipAddress">-</span></small></p>
</div>
</div>
<!-- Control Mode Section -->
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Control Mode</h5>
<div class="btn-group w-100" role="group">
<input type="radio" class="btn-check" name="mode" id="modeAnalog" value="analog" autocomplete="off">
<label class="btn btn-outline-primary" for="modeAnalog">DC Analog</label>
<input type="radio" class="btn-check" name="mode" id="modeDCC" value="dcc" autocomplete="off">
<label class="btn btn-outline-primary" for="modeDCC">DCC Digital</label>
</div>
</div>
</div>
<!-- DCC Address Section -->
<div class="card mb-3" id="dccSection" style="display: none;">
<div class="card-body">
<h5 class="card-title">DCC Configuration</h5>
<div class="row">
<div class="col-md-8">
<label for="dccAddress" class="form-label">Locomotive Address</label>
<input type="number" class="form-control" id="dccAddress" min="1" max="10239" value="3">
</div>
<div class="col-md-4">
<label class="form-label">&nbsp;</label>
<button class="btn btn-primary w-100" onclick="setDCCAddress()">Set</button>
</div>
</div>
</div>
</div>
<!-- Speed Control Section -->
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Speed Control
<span class="direction-indicator" id="directionIndicator"></span>
</h5>
<div class="speed-value" id="speedValue">0%</div>
<input type="range" class="form-range" id="speedSlider" min="0" max="100" value="0">
<div class="d-flex justify-content-between mt-3">
<button class="btn btn-danger" onclick="emergencyStop()">⏹ STOP</button>
<button class="btn btn-secondary" onclick="reverseDirection()">🔄 Reverse</button>
</div>
</div>
</div>
<!-- DCC Functions Section -->
<div class="card mb-3" id="functionsSection" style="display: none;">
<div class="card-body">
<h5 class="card-title">DCC Functions</h5>
<div id="functionButtons" class="d-flex flex-wrap">
<!-- Function buttons will be generated dynamically -->
</div>
</div>
</div>
<!-- WiFi Configuration Section -->
<div class="card">
<div class="card-body">
<h5 class="card-title">WiFi Configuration</h5>
<button class="btn btn-info w-100 mb-3" type="button" data-bs-toggle="collapse" data-bs-target="#wifiConfig">
Show WiFi Settings
</button>
<div class="collapse" id="wifiConfig">
<div class="mb-3">
<label class="form-label">WiFi Mode</label>
<select class="form-select" id="wifiMode">
<option value="ap">Access Point</option>
<option value="client">Client (Connect to Network)</option>
</select>
</div>
<div id="apSettings">
<div class="mb-3">
<label for="apSSID" class="form-label">AP SSID</label>
<input type="text" class="form-control" id="apSSID" value="LocoTestBench">
</div>
<div class="mb-3">
<label for="apPassword" class="form-label">AP Password</label>
<input type="password" class="form-control" id="apPassword" value="12345678">
</div>
</div>
<div id="clientSettings" style="display: none;">
<div class="mb-3">
<label for="wifiSSID" class="form-label">Network SSID</label>
<input type="text" class="form-control" id="wifiSSID">
</div>
<div class="mb-3">
<label for="wifiPassword" class="form-label">Network Password</label>
<input type="password" class="form-control" id="wifiPassword">
</div>
</div>
<button class="btn btn-warning w-100" onclick="saveWiFiSettings()">Save & Restart</button>
</div>
</div>
</div>
</div>
<script src="/js/bootstrap.bundle.min.js"></script>
<script src="/js/app.js"></script>
</body>
</html>

View File

@@ -0,0 +1,182 @@
let currentMode = 'analog';
let currentDirection = 1;
let dccFunctions = 0;
// Initialize
document.addEventListener('DOMContentLoaded', function() {
loadStatus();
setupEventListeners();
generateFunctionButtons();
setInterval(loadStatus, 2000);
});
function setupEventListeners() {
document.getElementById('speedSlider').addEventListener('input', function(e) {
updateSpeed(e.target.value);
});
document.querySelectorAll('input[name="mode"]').forEach(radio => {
radio.addEventListener('change', function(e) {
setMode(e.target.value);
});
});
document.getElementById('wifiMode').addEventListener('change', function(e) {
toggleWiFiSettings(e.target.value);
});
}
function generateFunctionButtons() {
const container = document.getElementById('functionButtons');
for (let i = 0; i <= 12; i++) {
const btn = document.createElement('button');
btn.className = 'btn btn-outline-secondary function-btn';
btn.id = 'f' + i;
btn.textContent = 'F' + i;
btn.onclick = () => toggleFunction(i);
container.appendChild(btn);
}
}
async function loadStatus() {
try {
const response = await fetch('/api/status');
const data = await response.json();
document.getElementById('statusIndicator').className = 'status-indicator status-connected';
document.getElementById('statusText').textContent = 'Connected';
document.getElementById('ipAddress').textContent = data.ip || '-';
currentMode = data.mode;
currentDirection = data.direction;
document.getElementById(data.mode === 'dcc' ? 'modeDCC' : 'modeAnalog').checked = true;
document.getElementById('speedSlider').value = data.speed;
document.getElementById('speedValue').textContent = data.speed + '%';
document.getElementById('dccAddress').value = data.dccAddress;
updateUIForMode(data.mode);
updateDirectionIndicator();
} catch (error) {
document.getElementById('statusIndicator').className = 'status-indicator status-disconnected';
document.getElementById('statusText').textContent = 'Disconnected';
}
}
function updateUIForMode(mode) {
currentMode = mode;
document.getElementById('dccSection').style.display = mode === 'dcc' ? 'block' : 'none';
document.getElementById('functionsSection').style.display = mode === 'dcc' ? 'block' : 'none';
}
async function setMode(mode) {
try {
await fetch('/api/mode', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({mode: mode})
});
updateUIForMode(mode);
} catch (error) {
console.error('Error setting mode:', error);
}
}
async function updateSpeed(speed) {
document.getElementById('speedValue').textContent = speed + '%';
try {
await fetch('/api/speed', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
speed: parseInt(speed),
direction: currentDirection
})
});
} catch (error) {
console.error('Error setting speed:', error);
}
}
async function emergencyStop() {
document.getElementById('speedSlider').value = 0;
updateSpeed(0);
}
async function reverseDirection() {
currentDirection = currentDirection === 1 ? 0 : 1;
updateDirectionIndicator();
const speed = document.getElementById('speedSlider').value;
updateSpeed(speed);
}
function updateDirectionIndicator() {
document.getElementById('directionIndicator').textContent = currentDirection === 1 ? '→' : '←';
}
async function setDCCAddress() {
const address = document.getElementById('dccAddress').value;
try {
await fetch('/api/dcc/address', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({address: parseInt(address)})
});
} catch (error) {
console.error('Error setting DCC address:', error);
}
}
async function toggleFunction(fn) {
const btn = document.getElementById('f' + fn);
const isActive = btn.classList.contains('btn-secondary');
if (isActive) {
btn.classList.remove('btn-secondary');
btn.classList.add('btn-outline-secondary');
} else {
btn.classList.remove('btn-outline-secondary');
btn.classList.add('btn-secondary');
}
try {
await fetch('/api/dcc/function', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
function: fn,
state: !isActive
})
});
} catch (error) {
console.error('Error setting function:', error);
}
}
function toggleWiFiSettings(mode) {
document.getElementById('apSettings').style.display = mode === 'ap' ? 'block' : 'none';
document.getElementById('clientSettings').style.display = mode === 'client' ? 'block' : 'none';
}
async function saveWiFiSettings() {
const mode = document.getElementById('wifiMode').value;
const config = {
isAPMode: mode === 'ap',
apSSID: document.getElementById('apSSID').value,
apPassword: document.getElementById('apPassword').value,
ssid: document.getElementById('wifiSSID').value,
password: document.getElementById('wifiPassword').value
};
if (confirm('Save WiFi settings and restart?')) {
try {
await fetch('/api/wifi', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(config)
});
alert('Settings saved. Device will restart...');
} catch (error) {
console.error('Error saving WiFi settings:', error);
}
}
}

File diff suppressed because one or more lines are too long