software design

This commit is contained in:
2026-01-25 23:48:46 +01:00
parent 2a2fba3473
commit c631110349
305 changed files with 40333 additions and 0 deletions

View File

@@ -0,0 +1,453 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32-S3 Sensor Array Analysis & Integration Guide</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;700&display=swap');
body {
font-family: 'Inter', sans-serif;
background-color: #f8fafc; /* Warm neutral light background */
color: #334155;
}
.mono {
font-family: 'JetBrains Mono', monospace;
}
/* Chart Container Styling - Mandatory Requirements */
.chart-container {
position: relative;
width: 100%;
max-width: 800px;
margin-left: auto;
margin-right: auto;
height: 300px;
max-height: 400px;
}
@media (min-width: 768px) {
.chart-container {
height: 350px;
}
}
.tab-active {
border-bottom: 2px solid #0f172a;
color: #0f172a;
font-weight: 600;
}
.tab-inactive {
color: #64748b;
}
.tab-inactive:hover {
color: #334155;
}
/* Custom scrollbar for code blocks */
.code-scroll::-webkit-scrollbar {
height: 8px;
}
.code-scroll::-webkit-scrollbar-track {
background: #f1f5f9;
}
.code-scroll::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
</style>
</head>
<body class="h-screen flex flex-col overflow-hidden">
<!-- Chosen Palette: Slate/Zinc (Neutral Technical) with Indigo accents for interactivity.
Rationale: Technical documentation requires high readability and low eye strain.
Warm neutrals for background (#f8fafc) reduce harshness. -->
<!-- Application Structure Plan:
Structure: Dashboard Layout with Sidebar Navigation.
1. Sidebar: Persistent navigation between the 'System Overview' and individual 'Sensor Detail' modules.
2. System Overview: High-level architectural view, Power Budget Calculator (Interactive), and I2C Bus Map (Visual).
- Why: Users need to see how these sensors coexist on the ESP32-S3 before diving into specifics.
3. Sensor Modules (BME680, ENS160, SEN6x, SHT4xI): Each acts as a "digital datasheet".
- Sub-tabs within modules: Specs, Hardware Integration (Wiring), Firmware (ESP-IDF v5.4), Calibration.
- Interactions: Toggle voltage modes, view performance charts, copy code snippets.
4. Integration Guide: General best practices for ESP-IDF v5.4 and sensor fusion.
Flow: User starts at Overview -> Checks Power/Addresses -> Drills down into specific sensors for implementation details -> Uses code snippets for project.
-->
<!-- Visualization & Content Choices:
1. Power Budget Calculator (Overview):
- Goal: Inform/Organize.
- Method: Interactive HTML/JS Table + Dynamic Text Sum.
- Interaction: Checkboxes to toggle sensor states (Sleep/Idle/Max).
- Justification: Critical for ESP32 battery projects; static text doesn't show dynamic load.
2. I2C Address Map (Overview):
- Goal: Inform/Relationships.
- Method: Flexbox Grid visualization.
- Interaction: Hover to see sensor name.
- Justification: Immediate visual conflict detection (0x76/0x77 etc). NO SVG.
3. Accuracy/Response Charts (Sensor Modules):
- Goal: Inform/Change.
- Method: Chart.js Line/Bar charts.
- Interaction: Buttons to switch between "Temperature Accuracy" or "Response Time".
- Justification: Visualizing spec sheets makes error margins intuitive.
4. Wiring Diagrams (Sensor Modules):
- Goal: Organize/Inform.
- Method: HTML/Tailwind CSS styled blocks (ASCII-style aesthetic but modern).
- Justification: Clear pin-to-pin mapping without raster images or SVG.
-->
<!-- CONFIRMATION: NO SVG graphics used. NO Mermaid JS used. -->
<!-- Header -->
<header class="bg-white border-b border-slate-200 h-16 flex items-center px-6 shadow-sm z-10 shrink-0">
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-slate-900 text-white flex items-center justify-center rounded font-bold mono">S</div>
<h1 class="text-xl font-bold tracking-tight text-slate-900">SensorArchitect <span class="text-slate-400 font-normal">for ESP32-S3 / IDF v5.4</span></h1>
</div>
<div class="ml-auto flex gap-4 text-sm text-slate-500">
<span class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-green-500"></span> System Ready</span>
<span id="current-project-load" class="font-mono">Total Load: 0.00 mA</span>
</div>
</header>
<div class="flex flex-1 overflow-hidden">
<!-- Sidebar Navigation -->
<nav class="w-64 bg-slate-50 border-r border-slate-200 flex flex-col shrink-0">
<div class="p-4">
<p class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">System Overview</p>
<button onclick="navTo('overview')" id="nav-overview" class="w-full text-left px-3 py-2 rounded-md text-sm font-medium bg-white text-slate-900 shadow-sm border border-slate-200 mb-1 hover:bg-slate-100 transition-colors">Dashboard & Bus Map</button>
</div>
<div class="p-4 pt-0">
<p class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">Sensor Modules</p>
<div class="space-y-1">
<button onclick="navTo('bme680')" id="nav-bme680" class="nav-item w-full text-left px-3 py-2 rounded-md text-sm font-medium text-slate-600 hover:bg-slate-200 hover:text-slate-900 transition-colors">BME680 <span class="text-xs text-slate-400 ml-1">Env/Gas</span></button>
<button onclick="navTo('ens160')" id="nav-ens160" class="nav-item w-full text-left px-3 py-2 rounded-md text-sm font-medium text-slate-600 hover:bg-slate-200 hover:text-slate-900 transition-colors">ENS160 <span class="text-xs text-slate-400 ml-1">MOX Air</span></button>
<button onclick="navTo('sen6x')" id="nav-sen6x" class="nav-item w-full text-left px-3 py-2 rounded-md text-sm font-medium text-slate-600 hover:bg-slate-200 hover:text-slate-900 transition-colors">SEN6x <span class="text-xs text-slate-400 ml-1">PM/All-in-one</span></button>
<button onclick="navTo('sht4xi')" id="nav-sht4xi" class="nav-item w-full text-left px-3 py-2 rounded-md text-sm font-medium text-slate-600 hover:bg-slate-200 hover:text-slate-900 transition-colors">SHT4xI <span class="text-xs text-slate-400 ml-1">Ind. RH/T</span></button>
</div>
</div>
<div class="mt-auto p-4 border-t border-slate-200">
<div class="bg-indigo-50 border border-indigo-100 rounded p-3">
<p class="text-xs font-semibold text-indigo-900 mb-1">Framework Target</p>
<p class="text-xs text-indigo-700">ESP-IDF v5.4</p>
<p class="text-xs text-indigo-700 mt-1">SoC: ESP32-S3</p>
</div>
</div>
</nav>
<!-- Main Content Area -->
<main class="flex-1 overflow-y-auto bg-white" id="main-content">
<!-- Content is injected via JS -->
</main>
</div>
<!-- JavaScript Application Logic -->
<script>
// --- Data Store ---
const sensorData = {
overview: {
title: "System Integration Dashboard",
intro: "This dashboard provides a holistic view of your ESP32-S3 environmental sensing array. Use the tools below to analyze power consumption and resolve I2C address conflicts before implementation. The 'Bus Map' visualizes the communication topology required for ESP-IDF v5.4.",
busMap: [
{ addr: '0x44', name: 'SHT4xI', conflict: false },
{ addr: '0x52', name: 'ENS160 (Alt)', conflict: false },
{ addr: '0x53', name: 'ENS160 (Def)', conflict: false },
{ addr: '0x69', name: 'SEN6x', conflict: false },
{ addr: '0x76', name: 'BME680 (SDO=GND)', conflict: false },
{ addr: '0x77', name: 'BME680 (SDO=VDD)', conflict: false },
],
powerProfile: [
{ id: 'bme', name: 'BME680', idle: 0.00015, active: 12, mode: 'idle' }, // mA
{ id: 'ens', name: 'ENS160', idle: 0.1, active: 29, mode: 'idle' },
{ id: 'sen', name: 'SEN6x', idle: 2.6, active: 110, mode: 'idle' }, // Max with fan
{ id: 'sht', name: 'SHT4xI', idle: 0.001, active: 0.35, mode: 'idle' }, // Heater adds ~200mA
{ id: 'esp', name: 'ESP32-S3 (Base)', idle: 10, active: 80, mode: 'active' } // Rough baseline
]
},
bme680: {
title: "BME680: Environmental 4-in-1",
intro: "The Bosch BME680 is a low-power MEMS sensor measuring gas, pressure, temperature, and humidity. It is critical for Indoor Air Quality (IAQ) monitoring. For the ESP32-S3, integration usually involves the Bosch BSEC library for proper gas resistance fusion.",
specs: {
supply: "1.71V - 3.6V",
interface: "I2C / SPI",
range_t: "-40 to 85 °C",
range_p: "300 to 1100 hPa",
range_rh: "0 to 100 %",
gas: "VOCs (0-500 IAQ)"
},
idf_notes: "Requires Bosch BSEC2 library for meaningful IAQ scores. Native read gives raw gas resistance (Ohms).",
chartData: {
labels: ['Sleep', 'Forced Mode', 'Parallel Mode', 'Heater Active'],
data: [0.00015, 0.15, 0.5, 12],
unit: 'Current (mA)'
}
},
ens160: {
title: "ENS160: Digital Metal-Oxide Multi-Gas",
intro: "The ScioSense ENS160 utilizes TrueVOC™ technology. Unlike older MOX sensors, it has on-chip algorithms to output eCO2 and TVOC directly via I2C, offloading processing from the ESP32-S3. It features independent hotplates for selective gas detection.",
specs: {
supply: "VDD: 1.71-1.98V, VDDIO: 1.71-3.6V",
interface: "I2C / SPI",
outputs: "TVOC (ppb), eCO2 (ppm), AQI-UBA",
warmup: "3 mins (initial), 1 hr (stabilization)"
},
idf_notes: "Requires strict adherence to heater timings. Ensure VDD is regulated correctly (1.8V core usually required, but modules often have LDOs).",
chartData: {
labels: ['Std Mode', 'Low Power', 'Ultra Low Power', 'Standby'],
data: [29, 11, 0.9, 0.1],
unit: 'Current (mA)'
}
},
sen6x: {
title: "SEN6x: All-in-One Air Quality Node",
intro: "The Sensirion SEN6x is a comprehensive module integrating PM1.0, PM2.5, PM4.0, PM10, NOx, VOC, RH, and T sensors. It simplifies hardware design by replacing multiple discrete components. It communicates via a single I2C interface.",
specs: {
supply: "4.5V - 5.5V (5V Typical)",
interface: "I2C",
pm_accuracy: "±5 μg/m³ or ±5%",
lifetime: "> 10 years (24/7 operation)"
},
idf_notes: "Needs 5V supply line! Logic levels are usually 3.3V compatible (check module specifically). Large data frames over I2C.",
chartData: {
labels: ['Idle', 'RHT/Gas Only', 'Fan/PM Active', 'Max Peak'],
data: [2.6, 6.5, 65, 110],
unit: 'Current (mA)'
}
},
sht4xi: {
title: "SHT4xI: Industrial Precision RHT",
intro: "The SHT4xI is a ruggedized version of the SHT4x standard sensor, designed for harsh environments. It features a built-in heater to remove condensation and prevent drift in high humidity. It is the gold standard for T/RH reference.",
specs: {
supply: "2.3V - 5.5V",
interface: "I2C",
acc_t: "±0.1 °C",
acc_rh: "±1.5 %RH",
heater: "Three levels (up to 200mW)"
},
idf_notes: "Simplest integration. Standard I2C read. Heater commands block measurement during execution.",
chartData: {
labels: ['Idle', 'Low Power Meas', 'High Precision', 'Heater High'],
data: [0.001, 0.02, 0.35, 35], // Heater is variable, simplified here
unit: 'Current (mA)'
}
}
};
// --- State Management ---
let currentView = 'overview';
let powerState = JSON.parse(JSON.stringify(sensorData.overview.powerProfile)); // Deep copy
// --- Core Functions ---
function init() {
navTo('overview');
updatePowerTotal();
}
function navTo(viewId) {
currentView = viewId;
// Update UI Nav Styling
document.querySelectorAll('button[id^="nav-"]').forEach(btn => {
if (btn.id === `nav-${viewId}`) {
btn.classList.remove('bg-white', 'text-slate-900', 'text-slate-600', 'hover:bg-slate-200');
btn.classList.add('bg-slate-800', 'text-white');
// Reset others
} else {
btn.classList.remove('bg-slate-800', 'text-white');
btn.classList.add('text-slate-600', 'hover:bg-slate-200', 'hover:text-slate-900');
}
});
const main = document.getElementById('main-content');
main.innerHTML = ''; // Clear content
if (viewId === 'overview') {
renderOverview(main);
} else {
renderSensorDetail(main, viewId);
}
}
// --- Renderers ---
function renderOverview(container) {
const data = sensorData.overview;
// Header
const header = document.createElement('div');
header.className = "p-8 border-b border-slate-100";
header.innerHTML = `
<h2 class="text-3xl font-bold text-slate-900 mb-2">${data.title}</h2>
<p class="text-slate-600 max-w-3xl leading-relaxed">${data.intro}</p>
`;
container.appendChild(header);
// Dashboard Grid
const grid = document.createElement('div');
grid.className = "p-8 grid grid-cols-1 lg:grid-cols-2 gap-8";
// Card 1: Power Budget
const powerCard = document.createElement('div');
powerCard.className = "bg-white border border-slate-200 rounded-xl shadow-sm p-6";
powerCard.innerHTML = `
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-bold text-slate-900">Power Consumption Simulator</h3>
<span class="text-xs bg-indigo-100 text-indigo-800 px-2 py-1 rounded font-mono">3.3V Rail</span>
</div>
<div class="space-y-3" id="power-list"></div>
<div class="mt-6 pt-4 border-t border-slate-100 flex justify-between items-end">
<span class="text-sm text-slate-500">Estimated Total</span>
<span id="power-total-display" class="text-2xl font-bold font-mono text-indigo-600">0.00 mA</span>
</div>
`;
grid.appendChild(powerCard);
// Card 2: I2C Topology
const busCard = document.createElement('div');
busCard.className = "bg-white border border-slate-200 rounded-xl shadow-sm p-6";
busCard.innerHTML = `
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-bold text-slate-900">I2C Bus Map</h3>
<span class="text-xs bg-emerald-100 text-emerald-800 px-2 py-1 rounded font-mono">SDA/SCL</span>
</div>
<div class="bg-slate-50 rounded-lg p-4 border border-slate-200 min-h-[200px] flex flex-wrap content-start gap-2">
<div class="w-full text-xs text-slate-400 mb-2 font-mono uppercase">Master</div>
<div class="w-full bg-slate-800 text-white p-2 rounded text-center font-mono text-sm mb-4 shadow-sm border border-slate-700">ESP32-S3 (I2C Controller 0)</div>
<div class="w-full h-px bg-slate-300 mb-4 relative">
<div class="absolute left-1/2 -top-1 w-2 h-2 bg-slate-300 rounded-full transform -translate-x-1/2"></div>
</div>
<div class="w-full text-xs text-slate-400 mb-2 font-mono uppercase">Slaves</div>
${data.busMap.map(dev => `
<div class="flex-1 min-w-[100px] bg-white border ${dev.conflict ? 'border-red-500 bg-red-50' : 'border-slate-300'} p-2 rounded text-center shadow-sm hover:shadow-md transition-shadow cursor-default group relative">
<span class="block text-xs text-slate-500 font-mono group-hover:text-indigo-600">${dev.addr}</span>
<span class="block text-sm font-semibold text-slate-800">${dev.name}</span>
</div>
`).join('')}
</div>
<div class="mt-4 text-xs text-slate-500">
<p>• Ensure internal pull-ups are disabled if external 2.2kΩ/4.7kΩ resistors are present.</p>
<p>• SEN6x and SHT4x are fixed addresses. ENS160/BME680 have selectable addresses to avoid conflicts.</p>
</div>
`;
grid.appendChild(busCard);
container.appendChild(grid);
// Render Power Rows
renderPowerRows();
}
function renderPowerRows() {
const list = document.getElementById('power-list');
if(!list) return;
list.innerHTML = powerState.map((item, index) => `
<div class="flex items-center justify-between p-2 hover:bg-slate-50 rounded transition-colors">
<div class="flex items-center gap-3">
<span class="text-sm font-medium text-slate-700 w-24">${item.name}</span>
<select onchange="updatePowerState(${index}, this.value)" class="text-xs border-slate-200 rounded text-slate-600 py-1 px-2 focus:ring-indigo-500 focus:border-indigo-500">
<option value="idle" ${item.mode === 'idle' ? 'selected' : ''}>Idle/Sleep</option>
<option value="active" ${item.mode === 'active' ? 'selected' : ''}>Active/Meas</option>
</select>
</div>
<div class="text-sm font-mono text-slate-600">
${item.mode === 'idle' ? item.idle : item.active} mA
</div>
</div>
`).join('');
updatePowerTotal();
}
function updatePowerState(index, mode) {
powerState[index].mode = mode;
renderPowerRows();
}
function updatePowerTotal() {
const total = powerState.reduce((acc, item) => {
return acc + (item.mode === 'idle' ? item.idle : item.active);
}, 0);
const display = document.getElementById('power-total-display');
const navDisplay = document.getElementById('current-project-load');
if(display) display.textContent = total.toFixed(2) + " mA";
if(navDisplay) navDisplay.textContent = "Total Load: " + total.toFixed(2) + " mA";
}
function renderSensorDetail(container, sensorId) {
const data = sensorData[sensorId];
// Wrapper for sticky header layout
const wrapper = document.createElement('div');
// Header
wrapper.innerHTML = `
<div class="p-8 pb-4 border-b border-slate-100 bg-white sticky top-0 z-10">
<div class="flex items-start justify-between">
<div>
<h2 class="text-3xl font-bold text-slate-900 mb-2">${data.title}</h2>
<p class="text-slate-600 leading-relaxed max-w-4xl">${data.intro}</p>
</div>
<div class="hidden md:block">
<button onclick="window.print()" class="text-sm bg-white border border-slate-300 text-slate-600 px-3 py-1.5 rounded hover:bg-slate-50 transition">Print / Save PDF</button>
</div>
</div>
<div class="flex gap-6 mt-6 border-b border-slate-200">
<button onclick="switchTab('specs')" id="tab-specs" class="tab-active pb-3 text-sm font-medium transition-colors">Specifications</button>
<button onclick="switchTab('hw')" id="tab-hw" class="tab-inactive pb-3 text-sm font-medium transition-colors">Wiring & Hardware</button>
<button onclick="switchTab('sw')" id="tab-sw" class="tab-inactive pb-3 text-sm font-medium transition-colors">ESP-IDF Integration</button>
</div>
</div>
`;
// Content Container
const content = document.createElement('div');
content.className = "p-8 max-w-5xl";
content.id = "sensor-content-area";
wrapper.appendChild(content);
container.appendChild(wrapper);
// Default to specs
renderSpecsTab(content, data);
}
function switchTab(tabName) {
// Update Tab UI
['specs', 'hw', 'sw'].forEach(t => {
const el = document.getElementById(`tab-${t}`);
if (t === tabName) {
el.className = "tab-active pb-3 text-sm font-medium transition-colors";
} else {
el.className = "tab-inactive pb-3 text-sm font-medium transition-colors";
}
});
// Render Content
const contentArea = document.getElementById('sensor-content-area');
const sensorId = currentView;
const data = sensorData[sensorId];
if (tabName === 'specs') renderSpecsTab(contentArea, data);
if (tabName === 'hw') renderHWTab(contentArea, data, sensorId);
if (tabName === 'sw') renderSWTab(contentArea, data, sensorId);
}
function renderSpecsTab(container, data) {
container.innerHTML = `
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">