Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

battery_control

One async trait — Battery — to monitor and control many different batteries, BMSes and power stations through a single normalized data model.

Devices span three loose classes (cell-level BMS, power stations, battery monitors) so every field is optional and control is capability-gated: unsupported commands return Error::Unsupported.

Each device family is a feature-gated backend adapter over an existing crate or a native protocol decoder — you only compile (and pull the deps of) what you use.

Backends

Feature Backend Class Transport Read Control
anker (default) anker_solix Anker SOLIX power station BLE SOC, ports, temp AC/DC ports
jk jk_bms JK BMS cell BMS serial + BLE (async) cells, temp, alarms MOSFETs, settings
jbd jbd_bms JBD / Xiaoxiang / Overkill cell BMS serial + BLE cells, temp, alarms charge/discharge MOSFETs
sok sok_bms SOK / ABC-BMS cell BMS BLE (EE + Modbus) cells, temps, SOC, capacity — (read-only)
renogy renogy_bms Renogy smart battery cell BMS BLE (BT-1/BT-2) cells, temps, SOC, capacity — (read-only)
daly dalybms Daly BMS cell BMS serial (async) SOC, MOSFET, capacity MOSFETs, SOC
victron victron_ble Victron monitor BLE broadcast SOC, V/I, temp, alarms — (read-only)
vedirect vedirect Victron VE.Direct monitor serial (BMV/SmartShunt) V/I, SOC, TTG — (read-only)
pylontech-can (default) native Pylontech CAN rack (EG4/SOK/…) CAN frames SOC, V/I/T, limits, alarms — (read-only)
can-socket Pylontech CAN via SocketCAN rack Linux CAN as above

full enables every host-buildable backend (everything except the Linux-only can-socket).

Example

use battery_control::{Battery, Command};
use battery_control::backends::AnkerBattery;

#[tokio::main(flavor = "multi_thread")]
async fn main() -> battery_control::Result<()> {
    let mut bat = AnkerBattery::connect("C1000", 6).await?;

    let s = bat.status().await?;
    println!("SOC {:?}%  out {:?} W", s.soc, s.power_out);

    if bat.capabilities().contains(battery_control::Capabilities::TOGGLE_PORTS) {
        bat.execute(Command::SetPort { id: "dc".into(), on: true }).await?;
    }
    Ok(())
}

Because every backend implements Battery, you can hold heterogeneous devices as Box<dyn Battery> and treat them uniformly:

let devices: Vec<Box<dyn battery_control::Battery>> = vec![
    Box::new(AnkerBattery::connect("C1000", 6).await?),
    Box::new(JkBattery::open_serial("/dev/ttyUSB0", 9600).await?),
];
for mut d in devices {
    println!("{}: {:?}%", d.info().backend, d.status().await?.soc);
}

The Pylontech decoder is transport-agnostic and pure — you can feed it CAN frames from any source:

use battery_control::backends::PylontechState;
let mut s = PylontechState::new();
s.feed(0x355, &[87, 0, 100, 0, 0, 0, 0, 0]); // SOC/SOH
s.feed(0x356, &voltage_current_temp_frame);
let status = s.to_status();

Data model

BatteryStatus normalizes across classes (current is +charge / -discharge):

  • basics: soc, soh, voltage, current, power_in/out, temperature_c
  • capacity: capacity_remaining_ah, capacity_full_ah, cycles, time_remaining_h
  • BMS: cells: Vec<CellInfo>, charging/discharging, charge/discharge_current_limit_a
  • stations: ports: Vec<PortInfo> — free-form ports (id, optional label, direction in/out/bidir, on, watts); no fixed port-type enum
  • alarms: Vec<String>

Command: SetPort, SetCharging, SetDischarging, SetBalancer, SetChargeLimit, SetSetting — each gated by Capabilities.

Adding a backend

See docs/PORTING.md for the full process — it defines the canonical crate API + CLI shape (matching jk_bms/anker_solix), the workspace layout for ported protocol crates (crates/<name>_bms), transport/BLE conventions, the Battery wrapper, the field map, and a checklist.

The short version:

  1. Pick a reuse tier (A: existing crate · B: Modbus + register map · C: custom protocol · D: crypto port) — see the backend roadmap.
  2. For B–D, add a crates/<name>_bms workspace crate with protocol.rs / transport/ / device.rs / a <name>tool CLI — same surface as jk_bms.
  3. Add src/backends/<name>.rs: a newtype adapter implementing [Battery] (map telemetry into BatteryStatus, advertise Capabilities, translate Commands), re-export from src/backends/mod.rs, and wire discovery.

Progress is tracked in the backend roadmap (JBD, SOK, Renogy, Seplos, PACE, Pylontech RS485, Victron, EcoFlow, Jackery, Bluetti).

Home Assistant

battery-ha-bridge exposes every connected battery to Home Assistant via MQTT Discovery — sensors, capability-gated switches and a charge-limit number entity appear automatically. It also ships as a Home Assistant OS add-on.

License

MIT

About

One async Rust trait to monitor & control many batteries/BMSes/power stations (Anker SOLIX, JK, Daly, Victron, Pylontech CAN) — backend-agnostic CLI, hardware-id identity.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages