ai-startup-skills
创业公司做 AI 产品的 7 步 Agent Skill 合集:验证 → BP → 政策 → 路演 → 获客 → 定价 → 注册。
Translation pending
ESP32 multi-source energy harvester (solar, wind, piezo) with a self-cleaning solar panel and an honest, offline Web Serial dashboard
OtherGitHub @Mukesh-240OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Repository information has not been collected yet.
No updates detected yet. GitHub releases will appear here.
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗An ESP32 multi-source energy harvester (☀️ solar · 🌬️ wind · 👣 piezo) with a self-cleaning solar panel, and a browser dashboard that is beautiful, works offline, and is honest about what it does not know.
What it is • Quick start • Tour • Hardware • Architecture • Honesty rules • Demo scenarios • Tests
Small renewable sources each make a little energy. A solar panel makes the most, but only while it is clean. A small wind generator makes energy in gusts. A piezo tile makes a tiny pulse with every footstep. HARVESTIQ puts all three on one ESP32, stores what they make in an 18650 battery, and keeps the panel clean with a vibration motor that runs when output drops while the light is still bright.
The laptop dashboard follows one loop:
| 🔍 SENSE | 🧠 DIAGNOSE | ⚙️ ACT | ✅ VERIFY |
|---|---|---|---|
| The ESP32 reads every source once per second and streams JSON over USB | The dashboard judges each sensor on its own evidence and explains anything suspicious | The ESP32 runs the cleaning cycle by itself when output is low in bright light | The dashboard compares solar output before and after cleaning and reports what changed |
[!IMPORTANT] The dashboard never controls the hardware. The firmware decides when to clean, and the browser only watches, explains and verifies. This is intentional: the sketch accepts no commands, so there are no fake motor buttons.
dist/harvestiq.html is the whole app. Double-click it and it runs, even offline.prefers-reduced-motion, and every animated state also has a text label.👉 Open the live demo (mirror: harvestiq-fawn.vercel.app), then pick any scenario and press Start. Both links can also connect to a real ESP32 plugged into the same computer.
git clone https://github.com/Mukesh-240/harvestiq.git
cd harvestiq
| Way to run | How | Notes |
|---|---|---|
| 📄 Single file (recommended) | Double-click dist/harvestiq.html | Works offline from file://. Web Serial works too. |
| 🖥️ Local server | npm start or double-click start.bat, then open http://localhost:8080 | Needs Node 20+ only for the 42-line static server |
[!WARNING] Use Chrome or Edge. Firefox and Safari do not support Web Serial. Don't double-click
index.html, because browsers block ES modules onfile://. Use the single-file build or the server instead.
flowchart LR
A["📥 Upload<br/>firmware/multi_energy_harvester/"] --> B["❌ Close Arduino<br/>Serial Monitor"]
B --> C["🌐 Open HARVESTIQ<br/>in Chrome / Edge"]
C --> D["🔌 Click<br/>Connect ESP32"]
D --> E["🎯 Pick the COM port<br/>(CP210x / CH340)"]
E --> F["✅ LIVE FROM ESP32<br/>in ~3 s"]
firmware/multi_energy_harvester/multi_energy_harvester.ino (firmware v3.2: solar + wind + piezo), select ESP32 Dev Module and your port, and upload.SOLAR + WIND + PIEZO HARVESTER v3.2."pz":1.| Symptom | Fix |
|---|---|
| Connect button disabled or a red notice | You are not in Chrome/Edge, or the page is not on http://localhost, https:// or file:// |
| "Could not open the port" | Close the Arduino Serial Monitor and any other serial program, then replug the cable |
| No board in the port list | Use a data cable (some cables only charge), and install the CP210x or CH340 driver |
| Connected but NO DATA | Baud must be 115200 and the sketch must be running. Press EN on the board and check the Serial console |
| Lines marked malformed | A truncated line or nan from printf. These are harmless, ignored and counted |
| Solar DISCONNECTED | The firmware reports ina:0. Check INA219 VCC, GND, SDA → GPIO21 and SCL → GPIO22 |
| Piezo NOT CONFIGURED | The board isn't sending "pz":1, which almost always means the old sketch is still on it (an older sketch, or an upload that failed with a compile error). Check the Serial console: v3.2 prints SOLAR + WIND + PIEZO HARVESTER v3.2. Re-upload v3.2 and wait for "Done uploading". Also make sure the dashboard is on LIVE, because demo scenario 1 shows NOT CONFIGURED on purpose |
| Piezo steps never increase | See Piezo tile: setup and calibration. Check the pin with PIN_FINDER, then tune STEP_JUMP_V |
| Link drops | Keep the tab visible, check the cable, and press Reconnect ESP32 |
The scene is drawn from data. The sun's rays follow the LDR reading, the turbine spins with generator voltage, energy flows
along the wires only while power is measured, and the panel shakes only while the ESP32 reports CLEANING. If a sensor is
unavailable, its part of the scene stops and is labelled. It never keeps animating as if it were healthy.
Measured values (solar V × I from the INA219) and estimates (wind power from V²/R, battery % from voltage) are labelled differently. A missing reading appears as "–" with the reason. It is never shown as zero.
Every channel is judged separately: solar, light, wind, piezo, battery, the ESP32 stream, the telemetry parser and cleaner feedback. A working USB stream never makes a sensor healthy by itself. Each tile shows the reading, how fresh it is, the error, what to try, and what the browser can't know.
Each diagnostic records its evidence, when it was first detected and last updated, a recommended action, the recovery condition and a confidence label. When it clears, it moves to Recovered incidents and stays in the history.
The charts are dependency-free SVG with 2-minute, 10-minute and session ranges. Telemetry gaps are drawn as gaps, not joined with a line. The console shows every raw line from the ESP32, colour-coded as telemetry, text or malformed.
flowchart LR
subgraph SOURCES["⚡ Energy sources"]
SUN["☀️ Solar panel"]
WIND["🌬️ DC motor<br/>(wind generator)"]
PZ["👣 Piezo tile"]
end
subgraph SENSE["📏 Sensing"]
INA["INA219<br/>V · I · P"]
LDR["LDR module"]
DIV1["Voltage divider"]
DIV2["Voltage divider"]
end
subgraph CORE["🧠 ESP32"]
ESP["ESP32 DevKit<br/>firmware v3.2"]
end
subgraph STORE["🔋 Storage"]
TP["TP4056 charger"]
BAT["18650 Li-ion"]
end
subgraph ACT["🧽 Self-cleaning"]
L298["L298N driver"]
VIB["Coin vibration motor"]
end
SUN --> INA -->|"I²C SDA 21 / SCL 22"| ESP
INA --> TP --> BAT
LDR -->|"GPIO 34"| ESP
WIND --> DIV1 -->|"GPIO 35"| ESP
PZ --> DIV2 -->|"GPIO 32"| ESP
BAT -->|"divider → GPIO 33"| ESP
ESP -->|"GPIO 25 → IN1"| L298 --> VIB
ESP ==>|"USB serial · 115200 baud<br/>1 JSON line / s"| PC["💻 HARVESTIQ<br/>dashboard"]
| ESP32 pin | Connected to | Purpose |
|---|---|---|
| GPIO 21 / 22 | INA219 SDA / SCL | Solar voltage, current, power (I²C) |
| GPIO 34 | LDR module AO | Ambient light (higher reading = darker) |
| GPIO 35 | Wind divider node | Generator voltage |
| GPIO 32 | Piezo divider node | Piezo capacitor voltage |
| GPIO 33 | Battery divider | 18650 voltage |
| GPIO 25 | L298N IN1 | Vibration cleaner motor |
| GPIO 26 | Status LED | On while cleaning |
| GPIO 27 / 14 / 12 | Solar / wind / piezo LEDs | Per-source activity |
The piezo discs feed a bridge rectifier that charges a 470 µF capacitor. A 100k / 22k divider brings the capacitor
voltage down to the ESP32's range on GPIO 32 (D32), and two red LEDs in series clamp the node. Firmware v3.2 ships
with PIEZO_CONNECTED true and reports "pz":1, so the dashboard enables the piezo channel on its own.
flowchart LR
T["👣 Footstep"] --> P["Piezo discs"] --> B["Bridge<br/>4 × 1N4007"] --> C["470 µF cap"]
C --> D["100k / 22k divider<br/>(÷ 5.55)"] -->|"GPIO 32"| E["ESP32"]
E --> S{"Quick rise above<br/>slow baseline<br/>> STEP_JUMP_V ?"}
S -- yes --> N["steps + 1<br/>400 ms lockout"]
S -- no --> L["baseline follows<br/>noise and leak"]
How a step is counted (v3.2): every 50 ms the firmware averages 32 ADC samples. It counts a step when the capacitor
voltage jumps above a slow, roughly 1-second baseline by more than STEP_JUMP_V. A 400 ms lockout makes the press
and the release of one footstep count as one step. Energy is estimated as ½·C·(V₂² − V₁²) for each step. The slow
leak through the divider (about 1 minute) and ADC noise are never counted as steps.
Not sure the piezo is on D32? Find the pin:
PIN_FINDER = true and upload.PINS : values jumps with each tap. That's your pin. If it isn't D32, set #define PIEZO_PIN to it.PIN_FINDER = false again and re-upload. Only use input pins that aren't already wired to something.Tune the step threshold:
peak rise on the PIEZO : line. Every second the log shows the largest jump above the baseline.STEP_JUMP_V to about half of a normal tap's peak rise. The default is 0.08 V.[!NOTE] A 470 µF capacitor rises only a little per footstep, and the rise per step gets smaller as the capacitor charges. If even a firm stomp shows a peak rise below about 0.03 V, the signal is close to ADC noise. A smaller capacitor (for example 47–100 µF) gives much larger steps.
stateDiagram-v2
direction LR
[*] --> IDLE
IDLE --> CHECKING: bright light AND<br/>solar current < DIRTY_MA (3 mA)
CHECKING --> IDLE: output recovers
CHECKING --> CLEANING: still low after ~2 s
CLEANING --> COOLDOWN: motor on for 5 s
COOLDOWN --> IDLE: wait 8 s
note right of CLEANING
The dashboard captures a
"before" baseline here and
an "after" sample once
COOLDOWN starts settling
end note
The panel never cleans in the dark or in dim light. Low output in low light is expected and is not treated as dirt.
sequenceDiagram
autonumber
participant HW as ☀️ Sensors
participant ESP as 🧠 ESP32
participant WS as 🔌 Web Serial
participant AD as 🧩 adapter.js
participant SS as 📦 Session
participant UI as 🖥️ Dashboard
HW->>ESP: analog + I²C readings
ESP->>WS: {"ldr":893,"light":"BRIGHT","ina":1,"sV":5.4,"smA":18, ...}
WS->>AD: one line at a time
AD->>AD: parse → validate ranges → missing ≠ 0
AD->>SS: normalized sample (each value: valid / unavailable / error)
SS->>SS: health monitor · diagnostics · verification
SS->>UI: render loop (scene, cards, health, charts)
Note over AD: The ONLY file that knows<br/>firmware field names
flowchart TB
subgraph Input["Data sources"]
SER["serial.js<br/>Web Serial transport"]
DEMO["demo.js<br/>17 deterministic scenarios<br/>+ firmware emulator"]
end
AD["adapter.js<br/>parseLine · normalizeTelemetry"]
subgraph Logic["Pure logic (no DOM, fully unit-tested)"]
HE["health.js<br/>SensorHealthMonitor"]
DI["diagnostics.js<br/>rules + lifecycle"]
VE["verification.js<br/>before / after compare"]
end
SE["session.js<br/>one Session per source<br/>(live and demo never mix)"]
subgraph View["UI"]
APP["ui/app.js<br/>bootstrap · render loop"]
R["ui/render.js · panels.js"]
H["ui/hero.js<br/>animated SVG scene"]
C["ui/charts.js<br/>SVG charts"]
end
EX["export.js<br/>CSV + printable report"]
SER --> AD
DEMO --> AD
AD --> SE
SE --> HE & DI & VE
SE --> APP --> R & H & C
SE --> EX
| Design decision | Why |
|---|---|
| Zero dependencies, no framework | Nothing to install or update, and it works offline on presentation day |
| One adapter file | If the firmware changes a field name, only adapter.js changes |
| Separate live and demo sessions | Simulated data can never leak into a live reading |
| Demo lines go through the real parser | The demo exercises the same code paths as the hardware |
| Virtual clock for the demo | Pausing or running at 4× never causes false "stale data" alerts |
Single-file build (build.mjs) | Inlines every module, so the app runs by double-clicking it from file:// |
These are the project's rules, and the code and tests enforce them:
Never fake a real sensor reading. Never silently hide a sensor failure. Never present a suspected fault as a confirmed hardware failure.
| ❌ What a typical dashboard does | ✅ What HARVESTIQ does |
|---|---|
Shows 0 mW when a sensor is missing | Shows "–" and the reason (missing, not detected, disabled in firmware…) |
| Marks everything green while the USB link is up | Judges each sensor separately. A healthy stream says nothing about the INA219 |
| Calls a dark room a "sensor fault" | Knows the LDR reads high in the dark. Darkness is not a failure |
| Says "panel cleaned, output +900 %!" | Says OUTPUT IMPROVED · data quality LIMITED, and notes that this is an association, not proof |
| Deletes an alert when it clears | Marks it RECOVERED and keeps it in the history |
| Mixes demo and live numbers | Uses separate sessions, an amber SIMULATED DATA banner, and tags every value source=demo |
| Status | Meaning |
|---|---|
| 🟢 HEALTHY | Valid, current readings, no problem detected |
| 🟡 WARNING | Needs attention, but the evidence is not conclusive |
| 🟠 STALE | No valid reading for more than 3.5 s while other channels still update |
| 🔴 DISCONNECTED | Cannot be read: no link, or the firmware reports the sensor missing |
| 🟣 SUSPECTED FAILURE | Evidence points to a fault but cannot prove it |
| ⚪ NOT CONFIGURED | Not enabled in this build (for example, the piezo). Never shown as zero |
| ⚫ UNKNOWN | Not enough information yet |
Every status also carries a confidence label: CONFIRMED (proven by a firmware report or by timing), SUSPECTED (a software inference) or UNDETERMINED (cannot tell).
ina=0 is CONFIRMED by the firmware. Firmware v3.2 re-checks the sensor every 0.5 s, so a loose wire after boot shows up within a second and blocks the cleaner, and the sensor recovers by itself when reconnected. (v3.1 checked only at boot.) A valid zero current is never treated as a failure.flowchart TD
S([ESP32 reports CLEANING]) --> B[Baseline = low-output samples<br/>flagged during CHECKING]
B --> W[Wait for cleaning + settling time]
W --> A[Take up to 4 'after' samples]
A --> Q1{Enough valid data?}
Q1 -- no --> X1[/"⚠️ INCONCLUSIVE<br/>SENSOR DATA INVALID"/]
Q1 -- yes --> Q2{Light changed by<br/>more than 400 LDR counts?}
Q2 -- yes --> X2[/"⚠️ INCONCLUSIVE<br/>CONDITIONS CHANGED"/]
Q2 -- no --> Q3{Current change ≥ 1 mA<br/>AND ≥ 15 %?}
Q3 -- "up" --> R1[/"✅ OUTPUT IMPROVED"/]
Q3 -- "down" --> R2[/"📉 OUTPUT DECREASED"/]
Q3 -- "no" --> R3[/"➖ NO SIGNIFICANT CHANGE"/]
Because the firmware confirms for only about 2 s at 1 Hz, the baseline has about 2 samples. Even a clear improvement is therefore rated data quality: LIMITED. A cycle that does not help raises a "no improvement after cleaning" diagnostic. HARVESTIQ never claims that cleaning caused a change, only that one followed it.
Click DEMO, pick a scenario, choose 1× / 2× / 4× and press Start. Nothing is simulated until you press Start.
Deep link: ?demo=<number>, for example ?demo=9.
| # | Scenario | What it demonstrates |
|---|---|---|
| 1 | Normal operation | Everything healthy; piezo NOT CONFIGURED (as with PIEZO_CONNECTED false) |
| 2 | Strong sunlight | Rays at full strength, high current, no warnings |
| 3 | Low sunlight | "Low output is consistent with low light". No cleaning suggested |
| 4 | Possible solar underperformance | Warning, a cleaning that does not help, the shade removed at 40 s, full recovery |
| 5 | Wind output fluctuating | Turbine speed follows voltage. Calm gaps say "output not detected", not "no wind" |
| 6 | Piezo pulse events | 12 footsteps. The tile presses only when a pulse is measured |
| 7 | Battery charging estimate | CHARGING → STABLE → DISCHARGING from the voltage trend |
| 8 | Cleaning cycle | IDLE → CHECKING → CLEANING (progress ring) → COOLDOWN (countdown) → IDLE |
| 9 | Cleaning → improved output | OUTPUT IMPROVED, data quality LIMITED |
| 10 | Cleaning, no improvement | Repeated NO SIGNIFICANT CHANGE and a "no improvement" diagnostic |
| 11 | INA219 communication failure | Solar DISCONNECTED (confirmed by firmware). The scene shows a warning, never zero |
| 12 | LDR stale / suspected failure | SUSPECTED FAILURE, then STALE when the field disappears |
| 13 | Wind data unavailable | STALE after 3.5 s, SUSPECTED after 30 s; other channels stay healthy |
| 14 | ESP32 disconnected | Telemetry timeout, all channels DISCONNECTED, link restored, history kept |
| 15 | Invalid telemetry | Truncated, NaN, wrong-type and impossible lines are ignored; parser health stays separate |
| 16 | Sensor recovery | INA219 and wind fail and return; incidents become RECOVERED and stay in history |
The demo's cleaner comes from an emulator of the firmware's own state machine. A bright panel making 40 mA can therefore never trigger a cleaning, exactly as on the real ESP32.
115200 baud, newline-delimited, one JSON line per second, mixed with human-readable text lines (text lines are shown in the console and ignored by the parser).
{"ldr":893,"light":"BRIGHT","ina":1,"sV":5.40,"smA":18.0,"smW":97.2,"wV":0.02,"wmW":0.0,
"cV":1.24,"steps":7,"puJ":361.2,"pz":1,"bV":3.83,"bPct":69,"panel":"CLEAN","cycles":0,"smWh":0.27,"wmWh":0}
| Field | Meaning | Note |
|---|---|---|
ldr, light | LDR raw value (0–4095, lower = brighter) and BRIGHT / DIM / NIGHT | The firmware's label is authoritative |
ina | INA219 detected (1 / 0) | 0 ⇒ solar fields are placeholders, shown as unavailable |
sV, smA, smW | Solar volts, milliamps, milliwatts | Firmware clamps negative current to 0 |
wV, wmW | Wind generator volts; power ≈ V² / 200 Ω | Power is an estimate |
cV, steps, puJ | Piezo capacitor volts, steps, energy | Placeholders while PIEZO_CONNECTED false; energy is an estimate |
pz | Piezo enabled in the firmware (1 / 0) | v3.2 only. Overrides the dashboard's "Piezo attached" setting |
bV, bPct | Battery volts, % estimate | % is recomputed from volts (3.0 V = 0 %, 4.2 V = 100 %) |
panel, cycles | Cleaner state and cycle count | The ESP32 is the source of truth |
smWh, wmWh | Energy integrated by the firmware | mWh |
v3.2 is built on the team's solar + wind sketch. It keeps that sketch's names, battery-only storage and settings, adds the piezo back, and fixes the problems below.
v3.1 (legacy/multi_energy_harvester_v3_1.ino) | Solar + wind sketch (no piezo) | v3.2 (firmware/multi_energy_harvester/) | |
|---|---|---|---|
| Serial header | MULTI-ENERGY HARVESTER + CLEANER v3.1 | SOLAR + WIND HARVESTER + CLEANER | SOLAR + WIND + PIEZO HARVESTER v3.2 |
| Piezo | Off (PIEZO_CONNECTED false) | Removed (sends cV, steps, puJ as 0) | On, D32 |
| Dashboard piezo status | NOT CONFIGURED unless Settings → Piezo attached is ticked | NOT CONFIGURED | HEALTHY: "pz":1 turns it on automatically |
| Step counting | Jump between two 50 ms samples; press + release can count twice | — | Rise above a slow baseline, 400 ms lockout, 32-sample averaging |
| Storage | Battery or supercap | 18650 battery only (BATT_PIN) | 18650 battery only (BATT_PIN) |
| Solar LED / "CHARGING" threshold | 1.0 mA | 0.05 mA | 0.05 mA (one INA219 step, so it can flicker on noise; the dashboard uses 1 mA) |
| INA219 check | Once at boot | Once at boot | Every 0.5 s, auto-recovers; a lost sensor blocks the cleaner |
| NaN in JSON | Possible | Possible | Never |
| Switches | — | — | HUMAN_LOG, BOOT_SELF_TEST, PIN_FINDER |
Pins, wiring, JSON field names and the cleaning timings are identical in all three, so the dashboard works with each of them.
npm test
140 tests run on Node's built-in test runner, with no test framework to install. The pretest step rebuilds the
single-file bundle and fails if dist/ is out of date.
| Area | What is covered |
|---|---|
| 🧩 Adapter | Parsing, range validation, missing ≠ zero, ina=0, implausible battery, firmware label precedence |
| 🩺 Health | Freshness, STALE and SUSPECTED timings, recovery after N valid readings, dark room ≠ fault |
| 🚨 Diagnostics | Rule triggers, lifecycle ACTIVE → RECOVERED, history is never deleted |
| ✅ Verification | All 5 outcomes, light-change detection, telemetry gaps, interrupted cycles |
| 🔌 Serial | Mocked Web Serial: line splitting, bad lines, USB unplug, a cleanly ended stream |
| 🎮 Demo | Single timer, pause / resume / speed, virtual clock, and every one of the 17 scenarios |
| 📦 Build | Every import resolves in the bundle, and the committed dist/ matches the sources |
harvestiq/
├── index.html UI shell
├── css/style.css light, natural design system (tokens → components)
├── src/
│ ├── config.js every threshold and plausibility range in one place
│ ├── adapter.js the ONLY firmware-specific code (JSON → internal model)
│ ├── model.js shared types: valid / unavailable / error values
│ ├── health.js Sensor Health Center logic ┐
│ ├── diagnostics.js rules + ACTIVE/RECOVERED lifecycle├ pure, no DOM
│ ├── verification.js before/after cleaning comparison ┘
│ ├── session.js one Session per source (live / demo)
│ ├── serial.js Web Serial transport
│ ├── demo.js firmware emulator + 17 scenarios
│ ├── export.js CSV + printable session report
│ ├── events.js event timeline
│ └── ui/ app wiring, renderers, animated scene, SVG charts
├── test/ 140 node:test tests
├── firmware/
│ └── multi_energy_harvester/
│ └── multi_energy_harvester.ino ESP32 sketch v3.2 (solar + wind + piezo on D32)
├── dist/harvestiq.html single-file build: double-click to run
├── build.mjs bundler (~80 lines, no dependencies)
├── serve.mjs static server for localhost:8080
├── docs/images/ README screenshots
├── vercel.json Vercel hosting (serves dist/harvestiq.html at /)
└── legacy/ original v3.1 sketch and first dashboard, kept for reference
"pz":1, and that the header reads SOLAR + WIND + PIEZO HARVESTER v3.2.LDR_BRIGHT and LDR_NIGHT in the sketch.STEP_JUMP_V.DIRTY_MA.pz (piezo enabled) so the Settings checkbox is no longer needed (v3.2)HARVESTIQ was built as an ECS college project: one embedded system, three renewable sources, and a dashboard that tells the truth.
Made with ☀️ 🌬️ 👣 by Mukesh-240
If this project helped or inspired you, consider giving it a ⭐
Released under the MIT License.
| 17 | Cleaning with changed light | INCONCLUSIVE: CONDITIONS CHANGED. Verification refuses to claim success |