Open-source multi-bay lithium cell charge/discharge tester: ESP32-S2 firmware, hub API, web dashboard, and PCB design.
Watch the build / overview video: https://youtu.be/NCjXZjViC6E
Disclaimer — use at your own risk
This project works with lithium cells, which can catch fire, explode, or cause serious injury or property damage if mishandled, shorted, overcharged, over-discharged, or used with a faulty build.
The designs, firmware, and software are provided as-is, with no warranty. By using this project you accept full responsibility for your build, testing, and operation. The author(s) take no responsibility for any damage, injury, loss, or other consequences that may result from using or modifying this project.
If you are not comfortable working with lithium batteries and power electronics, do not build or use this.
Full details: hardware/README.md
| File | Use it for |
|---|---|
hardware/gerber/capacity-tester-v1-gerber.zip |
Ordering PCBs — upload this Gerber zip to your preferred PCB house (6-layer) |
hardware/easyeda/capacity-tester-v1-easyeda-source.zip |
Editing the design — open the JSON files in EasyEDA (File → Open → EasyEDA…) |
Quick order path: download the Gerber zip → upload to your fab → set 6 layers. Optionally preview the zip in a Gerber viewer first.
Quick edit path: unzip the EasyEDA source → EasyEDA → File → Open → EasyEDA… → open schematic JSON, then PCB JSON → save into a project.
https://oshwlab.com/team_zpyafgse/project_tiylojme
OSHWLab can leave projects in review. While pending, the public page often 404s. Use the zip files above instead; check OSHWLab again later if you prefer their online viewer.
| Part | Path | Role |
|---|---|---|
| Firmware | src/, include/, platformio.ini |
Runs on each ESP32-S2 tester board |
| Hub | hub/ |
API, SQLite history, board polling, alerts |
| UI | ui/ |
Next.js dashboard (port 3000 → hub on 3001) |
| Hardware | hardware/ |
Gerbers, EasyEDA source, PCB notes |
Optional: YR1035_reader/ streams IR/voltage readings from a YR1035 meter into the hub.
- PlatformIO (VS Code extension or CLI) for firmware
- Node.js 20+ for hub and UI
- Python 3.10+ only if you use the YR1035 bridge
- A LAN where the hub host and ESP32 boards can reach each other
cd hub
cp .env.example .env # optional: Home Assistant webhooks
cp config.json.example config.json # optional; hub creates defaults if missing
npm install
npm startHub listens on http://localhost:3001 (override with PORT).
Board IPs start empty. Add them in the UI Settings panel (left → right order matches the physical bays).
In another terminal:
cd ui
npm install
npm run dev # development
# or: npm run build && npm start # productionOpen http://localhost:3000.
By default the UI talks to http://localhost:3001. If the hub runs on another machine, copy ui/.env.local.example → ui/.env.local and set:
NEXT_PUBLIC_HUB_URL=http://YOUR_HUB_IP:3001
NEXT_PUBLIC_HUB_WS=ws://YOUR_HUB_IP:3001From the repo root you can also use npm run install:all, npm run hub, and npm run ui:dev.
- Connect the ESP32-S2 over USB.
- Build and flash:
pio run -t upload
pio device monitor- Configure Wi-Fi and hub URL over serial (115200 baud):
wifi set <ssid> <password>
wifi hub http://YOUR_HUB_IP:3001
wifi status
When connected, the board prints its IP. Add that IP under Settings → Boards in the UI.
To bake defaults into a fresh flash, copy the example and edit locally:
cp include/wifi_defaults_private.h.example include/wifi_defaults_private.hSet SSID, password, and hub URL, then rebuild. Keep credentials in that private header on your machine only (it is gitignored). Values saved later with wifi set / wifi hub override these defaults.
- Start hub + UI.
- Open Settings.
- Add each board IP in left → right physical order.
- Set the pass threshold (mAh) if needed.
- Start a test from a channel card or use Start All.
cd YR1035_reader
pip install -r requirements.txt
HUB_URL=http://YOUR_HUB_IP:3001 python main.pyReadings appear on the UI /ir page. Override the WebSocket URL with HUB_WS if needed.
When every occupied bay is finished (COMPLETE or FAULT), the hub can POST once to a Home Assistant webhook. Out-of-range telemetry can use the same (or a dedicated) webhook.
- In
hub/.env:
HOME_ASSISTANT_SWAP_WEBHOOK_URL=http://homeassistant.local:8123/api/webhook/battery-tester-swap
# Optional:
# HOME_ASSISTANT_THERMAL_WEBHOOK_URL=http://homeassistant.local:8123/api/webhook/battery-tester-thermal- Example Home Assistant automation:
alias: Battery tester cells ready
trigger:
- platform: webhook
webhook_id: battery-tester-swap
allowed_methods:
- POST
local_only: true
action:
- service: notify.alexa_media_everywhere
data:
message: "{{ trigger.json.message }}"
data:
type: announceRestart the hub after changing .env. Use Settings → Send test alert in the UI to verify.
Range alerts (edge-triggered, with hysteresis on temperatures):
- Cell temperature outside 15–38 °C (occupied bays)
- Heatsink temperature outside 15–50 °C
- Cell voltage outside the chemistry charge/cutoff window (±150 mV)
- Invalid cell temperature sensor on an occupied bay
# Hub
cd hub && npm install && npm start
# UI (separate process)
cd ui && npm install && npm run build && npm startKeep both processes running with systemd, PM2, or similar. Across updates, preserve:
hub/data.db(+ WAL/SHM) — history and board IP listhub/config.json— runtime settingshub/.env— secrets / webhooks
Useful URLs:
http://HOST:3000 # dashboard
http://HOST:3000/history
http://HOST:3000/ir
http://HOST:3001/api/results
http://HOST:3001/api/runs
├── src/ / include/ ESP32-S2 firmware (PlatformIO)
├── hub/ Express + SQLite API (port 3001)
├── ui/ Next.js dashboard (port 3000)
├── hardware/ Gerbers, EasyEDA source, PCB notes
├── YR1035_reader/ Optional meter → hub bridge
└── platformio.ini
Hub and UI are separate packages on purpose: the hub is a long-running Node service with SQLite and WebSockets; the UI is a standard Next.js app.
MIT — see LICENSE. This license does not reduce the risks described in the disclaimer above; use of the project remains at your own risk.