Skip to content

mti-sk/flatpack2

Repository files navigation

flatpack2

CLI + Web-GUI controller for Eltek Flatpack2 48V/2000W HE rectifiers via CAN bus

Tested on real hardware with Waveshare USB-CAN-A adapter (STM32, CH341).


⚠️ Disclaimer

This project is tested and works on real hardware, but it is experimental software based on reverse engineering of an undocumented CAN protocol. It is provided as-is, with no warranty, and no liability for any damage resulting from its use – including damage to your PSU, battery, or other connected equipment. Not affiliated with Eltek. You use this software entirely at your own risk.

Full text: DISCLAIMER.md


Features

  • CAN communication via Waveshare USB-CAN-A (native binary protocol, not slcan)
  • Auto-detection of adapter by USB VID:PID 1a86:7523
  • Virtual PTY terminal – connect with screen /tmp/flatpack2.pty from any SSH session
    • Top status bar for warnings/errors (PSU ALARM, CAN loss/reconnect, charger error stops) – color-coded, timestamped, drawn on row 1 via ANSI scroll region so async events never corrupt the prompt
  • Web-GUI dashboard – mobile-first dark theme, live SSE updates every 3 s (configurable sse_interval)
    • Live Vout·Iout in the header at all times; Ah·Wh added in charger mode
    • PSU status (Vout, Iout, Vin, temperatures, mode)
    • Set voltage / current with confirmation dialog
    • Standby button – sets PSU to 48.0 V / 0.1 A safe idle state (values shown inline)
    • LiFePO4 charger control (start / stop / change current / reset / SOC progress bar / stop reason)
    • Battery parameters card – static config overview (cells, voltages, currents, limits)
    • Live graphs: Vout+Iout, Delivered capacity [Ah], Delivered energy [Wh] – time window 15 min / 1 h / 12 h
    • CC→CV transition annotation in graph
    • Live log tail
    • CSV history export (/api/history)
  • CSV history persistence (optional) – samples written to disk (buffered), last 12 h reloaded into the graphs on restart, automatic retention trim
  • LiFePO4 CC/CV charger with soft-start and safe battery detection
    • DETECT phase: PSU holds low detect_voltage at minimal detect_current; battery detected via voltage rise on output (no inrush current)
    • RAMP phase: voltage stepped up from V_bat to target in configurable steps; avoids inrush spikes on connection
    • CC phase: constant current until target voltage reached
    • CV phase: voltage held, current tapers until charge_current_tail
    • Battery-disconnect detection in every phase (RAMP/CC/CV) returns to DETECT automatically; Ah/Wh counters are preserved
    • Change charge current on the fly and reset the cycle (CLI + Web-GUI)
    • Rough SOC estimate – OCV anchor at detection + coulomb counting
    • ALARM safety – PSU alarm in any phase aborts the charge and puts the PSU into standby (manual restart required)
    • Auto-start on program startup and after CAN reconnect (configurable)
  • Standby mode – sets PSU to 48.0 V / 0.1 A; overwrites restore values so reconnect keeps standby
  • Value restore on reconnect – last set voltage/current automatically re-applied after CAN bus recovery
  • Charge resume on reconnect – if charging was active before CAN loss, it resumes automatically
  • Daemonization – double-fork, PID file, systemd service included
  • CAN watchdog – auto-reconnect on communication loss
  • Per-PSU startup config – serial number mapping, auto-apply on discovery
  • Configurable power ratingpower_rating (1800/2000/3000 W) derives enforced current/power limits for your specific PSU variant
  • Log rotationRotatingFileHandler
  • SIGHUP reloads log level from the config file specified at startup (--config)

Hardware

Component Details
PSU Eltek Flatpack2 48V / 2000W HE
Adapter Waveshare USB-CAN-A (Model A, STM32 + CH341)
CAN speed 125 kbit/s
Serial baudrate 2 000 000 baud
USB VID:PID 1a86:7523

CAN protocol (confirmed by hardware testing)

Frame Arbitration ID Notes
LOGIN TX 0x05004804 Keepalive every 1 s; PSU times out after 15 s
STATUS RX (arb & 0xFFFFFF00) == 0x05014000 8-byte status frame
SET TX 0x05FF4004 Broadcast – only this address works
ALERT 0x0501BFFC Alert request/response

SET data format: struct.pack("<HHHH", iout_da, vout_cv, vout_cv, ovp_cv)

  • iout_da = current × 10 (deciamps)
  • vout_cv = voltage × 100 (centivolts)
  • ovp_cv = OVP voltage × 100

Frame format (Waveshare binary protocol):

AA  (E0|len)  [ID 4 bytes LE]  [data 0–8 bytes]  55

Requirements

  • Python 3.8+
  • Linux (uses /dev/ttyUSB*, sysfs, pty)
  • User must be in dialout group (or run as root)
pip install -r requirements.txt
# pyserial>=3.5
# flask>=3.0

Installation

Option A – pipx (recommended, isolated environment)

# Install pipx if not already installed
sudo apt install pipx
pipx ensurepath

# Install flatpack2 directly from GitHub
pipx install git+https://github.com/mti-sk/flatpack2.git

# Run
flatpack2 --config /etc/flatpack2/flatpack2.conf

The wheel bundles the config templates, systemd unit and install scripts. After installation run:

flatpack2 --files

This prints the directory with the bundled files (typically ~/.local/share/pipx/venvs/flatpack2/share/flatpack2/) together with ready-to-paste commands for copying the config to /etc/flatpack2/ and the service unit to /etc/systemd/system/ (including the correct ExecStart= path for your pipx binary). You decide what to copy where - nothing is written outside the pipx venv automatically.

To upgrade to the latest version:

pipx upgrade flatpack2

To uninstall:

pipx uninstall flatpack2

Option B – install.sh (venv + systemd, automated)

# Clone the repository
git clone https://github.com/mti-sk/flatpack2.git
cd flatpack2

# Install (PSU controller only)
sudo bash install.sh

# Or install with LiFePO4 charger config
sudo bash install.sh --config flatpack2_charger.conf

The script will:

  • Create a Python virtual environment at
  • Install dependencies (pyserial, flask)
  • Copy program files to
  • Copy config to (only if not already present)
  • Create wrapper
  • Install and start systemd service
  • Add current user to group

Upgrade – run the script again; existing config is preserved:

sudo bash install.sh

Uninstall:

# Keep config files
sudo bash uninstall.sh

# Remove everything including config
sudo bash uninstall.sh --purge

Option C – manual (foreground / development)

git clone https://github.com/mti-sk/flatpack2.git
cd flatpack2

# Create venv and install dependencies
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# Add user to dialout (logout/login required)
sudo usermod -aG dialout $USER

# Run in foreground
python3 flatpack2.py
screen /tmp/flatpack2.pty

Using charger configuration

python3 flatpack2.py --config flatpack2_charger.conf

systemd service (manual setup)

sudo cp flatpack2.service /etc/systemd/system/
sudo nano /etc/systemd/system/flatpack2.service   # adjust paths if needed
sudo systemctl daemon-reload
sudo systemctl enable flatpack2
sudo systemctl start flatpack2
sudo systemctl status flatpack2
journalctl -u flatpack2 -f

Configuration

Two config files are provided:

File Purpose
flatpack2.conf PSU controller only, charger section commented out
flatpack2_charger.conf Same as above + LiFePO4 charger enabled

Specify config file with --config:

python3 flatpack2.py --config flatpack2_charger.conf

Key sections

[can]
channel          = /dev/ttyUSB0   # tried first; autodetect fallback
bitrate          = 125000         # Flatpack2 CAN speed
serial_baudrate  = 2000000        # Waveshare adapter serial speed
autodetect       = true           # find adapter by USB VID:PID

[psu]
ovp_voltage      = 60.0           # over-voltage protection (V)
discovery_timeout = 10            # seconds to wait for PSU on startup
power_rating     = 2000           # PSU power variant (W): 1800 / 2000 / 3000
                                   # derives enforced I_MAX = power_rating / 48.0V

[webgui]
enabled          = true
host             = 0.0.0.0        # 127.0.0.1 for localhost only
port             = 8080
log_access       = true

[daemon]
enabled          = false
pidfile          = /var/run/flatpack2.pid

[terminal]
type             = pty
pty_symlink      = /tmp/flatpack2.pty

Per-PSU startup configuration

[PSU_1]
serial           = 173350049492   # serial number (from 'map' command)
voltage          = 54.0           # V
current          = 20.0           # A
apply_on_start   = true           # apply after stable status detected
                                  # ignored when charger auto_start = true

Serial number is matched against the hex bytes reported by the PSU hello frame. If serial is omitted, mapping is by discovery order (first seen = PSU_1).

LiFePO4 charger configuration

[charger]
cell_count           = 16       # cells in series
cell_voltage_max     = 3.60     # V per cell (CV target = cell_count × cell_voltage_max)
capacity             = 400      # Ah (informational)
charge_current       = 35.0     # A (CC phase)
charge_current_tail  = 4.0      # A (end-of-charge detection in CV phase)
safety_time_limit    = 1200     # minutes (20 h) – counted from RAMP start
detect_voltage       = 48.0     # V – PSU output during DETECT phase
detect_current       = 0.2      # A – PSU current during DETECT phase
detect_threshold     = 1.0      # V – vout must exceed detect_voltage + detect_threshold
min_current_detect   = 0.5      # A – detection confirmation AND "current collapsed"
                                # threshold for battery-disconnect detection in CC/CV
disconnect_detect_time = 5.0    # s – iout must stay below min_current_detect this long
                                # in CC before the battery is deemed disconnected
ramp_step_voltage    = 0.1      # V per ramp step
ramp_step_interval   = 5.0      # seconds between ramp steps
voltage_tolerance    = 0.1      # V – CC→CV transition threshold
monitor_interval     = 5.0      # seconds between monitor refreshes
auto_start           = true     # start charging automatically on program start
                                # and after CAN reconnect (default: true)

Charger phases

Phase Description
detect PSU holds detect_voltage at detect_current. Battery detected when vout > detect_voltage + detect_threshold. No inrush current.
ramp Voltage steps up from V_bat by ramp_step_voltage every ramp_step_interval s. Transitions to CC when iout >= charge_current, or to CV when target reached. Battery disconnect returns to detect.
CC Full charge_current applied (PSU in current limit). The voltage setpoint keeps stepping by ramp_step_voltage up to target_voltage – it is never jumped in one step, which would make the PSU's current limiter overshoot and overload the unit. Ends when vout >= target_voltage - voltage_tolerance. If current collapses to ≤ min_current_detect for disconnect_detect_time s, the battery is deemed disconnected and the charger returns to detect (Ah/Wh preserved).
CV Target voltage held. A gradual taper to charge_current_tail ends the charge (done); a sudden current collapse is treated as a disconnect and returns to detect.
done Charging finished when iout <= charge_current_tail via a normal taper.
error Stopped due to PSU ALARM (any phase), high temp, or safety timeout. Requires a manual charge start / charge reset to clear.

Auto-start behaviour

When auto_start = true (default):

  • Charging starts automatically after PSU discovery on program startup.
  • After a CAN bus reconnect, charging resumes automatically only if it was active before the disconnection. If the user stopped charging manually before the outage, it will not restart.
  • When auto-start is active, apply_on_start in [PSU_x] sections is effectively superseded by the charger.

Changing the charge current / resetting

The charge current can be changed at any time without stopping the cycle: charge current <A> (CLI) or the Set I button in the Web-GUI. In RAMP/CC/CV the new current is sent to the PSU immediately; in DETECT it is stored and used once the battery is detected.

charge reset (CLI) / Reset button clears the Ah/Wh counters, restores the charge current to the config default and restarts the cycle from DETECT. Note that a battery disconnect during charging does not clear Ah/Wh – only an explicit reset does.

SOC estimate

The reported state-of-charge is a rough estimate, always labelled as such. LiFePO4 has a very flat voltage curve and the voltage is elevated under charge current, so voltage alone is unreliable. Instead:

  • at battery detection the current is near zero, so vout ≈ OCV; a per-cell OCV→SOC table (linear interpolation) gives the starting SOC,
  • from there the estimate tracks by coulomb counting (charged Ah / capacity),
  • in CV it is pulled toward 100 % as the current tapers to charge_current_tail.

Accuracy depends on a correct capacity value and on the battery resting near OCV at detection; treat it as indicative, not a fuel gauge.


History persistence (CSV)

By default the measurement history lives only in RAM (12 h ring buffer). Enable persistence to keep it across restarts:

[history]
persist        = true                        # off by default
file           = /var/log/flatpack2_history.csv
flush_interval = 60                           # s – buffered writes to spare flash
retention_days = 30                           # rows older than this trimmed at startup
  • Columns: timestamp_ms,vout,iout,ah,wh.
  • Writes are buffered and flushed every flush_interval seconds (and once on shutdown) to avoid hammering an SD card / flash storage.
  • On startup the last 12 h are loaded back into the graphs, and rows older than retention_days are dropped (atomic rewrite via a temp file).
  • The bundled flatpack2_charger.conf enables persistence; flatpack2.conf ships it enabled too. Set persist = false to keep the previous RAM-only behaviour.

Standby mode

Standby sets the PSU to a safe low-power idle state:

Parameter Value
Voltage 48.0 V
Current 0.1 A (PSU minimum)

Standby overwrites the restore values, so after a CAN bus outage the PSU will return to standby rather than to a previously set charging voltage. This is intentional – use charge start or set to resume normal operation after standby.


Usage

Foreground with PTY terminal

# Terminal 1 – start program
python3 flatpack2.py

# Terminal 2 – connect to PTY
screen /tmp/flatpack2.pty

Daemon mode

python3 flatpack2.py --daemon
screen /tmp/flatpack2.pty     # connect to running daemon

Web-GUI

Open in browser: http://<host>:8080

Default is 0.0.0.0:8080 – accessible from any device on the local network. For localhost-only access set host = 127.0.0.1 in [webgui].


CLI Commands

Connect via PTY (screen /tmp/flatpack2.pty) or use stdio mode.

help                    Show help
get                     Show status of all PSUs
get <id>                Show status of PSU with given ID
set <id|all> <V> <I>    Set voltage (V) and current (A)
standby                 Set ALL PSUs to standby (48.0 V / 0.1 A)
standby <id>            Set PSU <id> to standby
map                     Show serial → ID mapping

charge start [I]        Start LiFePO4 charging (optional current override)
charge stop             Stop charging
charge current <A>      Change charge current while charging
charge reset            Clear Ah/Wh, restore default current, restart from DETECT
charge status           Show charge status (incl. SOC estimate)
charge monitor          Continuous monitor (Enter to stop)
charge config           Show charger configuration
charge battery          Show battery parameters (static config)

shutdown                Stop program

Examples:

get
get 1
set all 54.0 20.0
set 1 48.0 10.0
standby
standby 1
map
charge start
charge start 15.0
charge current 20.0
charge reset
charge status
charge battery
charge monitor

Web-GUI API

All endpoints return JSON unless noted.

Method Endpoint Description
GET / Dashboard HTML
GET /events SSE stream (interval = sse_interval, default 3 s)
GET /api/status Single status snapshot (JSON)
POST /api/set Set voltage/current
POST /api/standby Set PSU to standby (48.0 V / 0.1 A)
POST /api/charge/start Start charging
POST /api/charge/stop Stop charging
POST /api/charge/current Change charge current while charging
POST /api/charge/reset Clear Ah/Wh, restore default current, restart from DETECT
GET /api/history Download history as CSV

POST /api/set

{ "voltage": 54.0, "current": 20.0 }

POST /api/charge/start

{ "current": 15.0 }   // optional; omit to use config default

POST /api/charge/current

{ "current": 20.0 }   // required; must be within the PSU current limit

POST /api/charge/reset

{}   // no body required

POST /api/standby

{}   // no body required

GET /api/history

?window=900    // seconds; omit for full 12h history

Returns CSV: timestamp_ms,vout,iout,ah,wh


PSU Limits (Flatpack2 48V family)

The Eltek Flatpack2 48V rectifier is produced in several power variants. The voltage range and CAN protocol are identical across variants; only the max current/power differs. Set power_rating in [psu] to match your hardware – this drives the software-enforced current/power limits.

power_rating Voltage Current (I_MAX) Power
1800 W 43.5 – 57.6 V ~37.5 A 1800 W
2000 W (default) 43.5 – 57.6 V ~41.7 A 2000 W
3000 W 43.5 – 57.6 V ~62.5 A 3000 W

I_MAX is derived as power_rating / 48.0 V (nominal). Current is automatically limited if V × I would exceed the configured power.

Note: setting power_rating only changes the software limit check – it does not change what your physical unit is actually rated for. Make sure the value matches the label on your PSU; see DISCLAIMER.md.


Signals

Signal Action
SIGTERM Graceful shutdown
SIGHUP Reload log level from config (uses --config path from startup)

File Structure

flatpack2.py             Main program
flatpack2.conf           Configuration – PSU controller only
flatpack2_charger.conf   Configuration – PSU controller + LiFePO4 charger
flatpack2.service        systemd service unit
requirements.txt         Python dependencies
README.md                This file
DISCLAIMER.md            Legal disclaimer (no warranty, no liability, experimental software)

Known limitations

  • Single PSU only. The internal ID/serial mapping infrastructure is prepared for multiple rectifiers, but _handle_status() currently attributes all STATUS frames to PSU 1 and login uses a fixed arbitration ID for every unit. With more than one Flatpack2 on the bus, readings from different units would be mixed into a single record. Proper multi-PSU support requires per-unit login IDs and STATUS dispatching, which cannot be implemented safely without hardware testing with multiple rectifiers.
  • Assumption: vout in the STATUS frame reflects the actual output terminal voltage even when the PSU is passive (battery backfeed). This matches observed behaviour but has not been exhaustively verified.
  • power_rating variants 1800 W / 3000 W are validated logically (limit math only); only the 2000 W variant has been confirmed on real hardware so far.
  • The Web-GUI has no authentication and uses the Flask development server. Keep it on a trusted LAN (host = 0.0.0.0) or bind to 127.0.0.1 and use an SSH tunnel / reverse proxy if exposure is a concern.

Hardware confirmed working (real Flatpack2 48V/2000W HE + Waveshare USB-CAN-A): CAN frame format, LOGIN keepalive, broadcast SET, STATUS parsing, serial-number mapping, startup apply, value restore after CAN reconnect, and the full DETECT → RAMP → CC → CV charger flow.

For per-version changes see CHANGELOG.md.

About

CLI + Web-GUI controller for Eltek Flatpack2 48V HE rectifiers via CAN bus

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors