A small Linux tool · BLE · LiFePO4 · 2026
A one-shot Go binary that connects to a JK-BMS over Bluetooth Low Energy, reads one cell-info frame, and writes JSON. Built for off-grid solar setups where the BMS has no cable — only BLE — and you still want graphs in Grafana, sensors in Home Assistant, alerts on Telegram.
A modern JK-BMS will happily report every cell voltage, the pack current, MOSFET temperature, balance state, and SOC — through its phone app, over BLE. There is no RS485, no CAN, no UART pad you can solder to. The only way in is the Bluetooth radio.
That's fine while you're standing next to the battery with the JK app open. It's not fine when the pack lives in a shed, runs an inverter you'd like Home Assistant to know about, and you'd rather not babysit it from your phone.
Run it as a cron job from any Linux host with a Bluetooth radio that's in range of the battery — a Raspberry Pi, an OpenWrt router, a salvaged laptop. It scans, connects, reads one frame, writes a file, exits. Polling and persistence happen elsewhere; the binary stays small, single-purpose, and recovers from whatever cleanly by simply running again.
Pick whichever fits the host. The script is the fastest. Source build
gives you full control. go install is for Go shops. All three
land the same Linux binary; the BLE backend won't cross-compile to macOS
or Windows.
# auto-detects amd64 / arm64 / armv7 / armv6, # verifies SHA-256, installs to /usr/local/bin $ curl -fsSL https://raw.githubusercontent.com/\ tggo/jkbms-poll/main/install.sh | sh # pin a version, or change install dir $ curl -fsSL …/install.sh | sh -s -- v0.1.0 $ curl -fsSL …/install.sh | INSTALL_DIR=$HOME/bin sh
# lands in $GOBIN (or $HOME/go/bin) $ go install github.com/tggo/jkbms-poll@latest # cross-build from a Mac for a Pi $ GOOS=linux GOARCH=arm64 \ go build -o jkbms-poll \ github.com/tggo/jkbms-poll@latest
$ git clone https://github.com/\ tggo/jkbms-poll $ cd jkbms-poll $ make test # parser unit tests $ make build # cross-compile arm64 $ make deploy # scp to $DEPLOY_HOST
$ JKBMS_MAC=AA:BB:CC:DD:EE:FF jkbms-poll -log debug level=INFO msg="scan locked target" rssi=-65 level=INFO msg="connect ok" attempt=1 took=420ms level=INFO msg="parsed pack" v=53.224 a=31.881 soc=25 t1=13.4 wrote /tmp/jkbms.json (V=53.224 I=31.881A SOC=25% Δ=0.017V crc_ok=true)
Every poll writes a flat JSON object — pack-level numbers, per-cell
voltages and resistances, balance state, MOSFET state, temperatures,
SOC / SOH / capacity / cycle count / total runtime. Plus
raw_frame_hex, the full 300-byte frame, so when JK ships
firmware that nudges an offset by two bytes you can re-parse the
last six months of poll history without a working radio.
Below is the literal output of one run, trimmed for readability. The CRC field, the schema version, and a UTC timestamp travel with every record.
{
"timestamp_iso": "2026-05-10T00:13:20Z",
"bms_address": "AA:BB:CC:DD:EE:FF",
"crc_ok": true,
"battery_voltage_v": 53.224,
"battery_current_a": 31.881, // +charge / -discharge
"soc_percent": 25,
"cell_voltages_v": [3.333, 3.326, 3.326, …],
"cell_min_v": 3.323,
"cell_max_v": 3.337,
"cell_delta_v": 0.017,
"power_tube_temp_c": 12.9,
"t1_c": 13.4, "t2_c": 12.8,
"cycle_count": 9,
"raw_frame_hex": "55aaeb9002ac…"
}
The pack is in a shed twenty metres from the house. A Pi Zero 2 W on the wall behind the inverter polls every minute, drops JSON in a tmpfs, MQTT publishes it to a broker on the main router. From the kitchen you can see a cell going out of balance before it starts dragging the others down.
Your inverter only talks to the main pack over RS485. The expansion pack has a JK BMS with BLE only. Drop a tiny Linux box near the second battery, run jkbms-poll on a timer, surface SOC and per-cell voltages in the same Home Assistant dashboard as the inverter.
Cron writes each frame to disk with a timestamp. Six months later you have a per-cell voltage timeline that tells you exactly which cell is ageing fastest, when imbalance starts climbing under load, and whether the balancer is actually doing anything between cycles.
A budget BLE-only BMS in a vehicle is the norm. Run jkbms-poll on the existing Linux router, expose a tiny dashboard over Wi-Fi. When you're 200 km from a hardware store, knowing whether you're chasing a balance issue or a real cell failure is the difference.
One cron, one tiny shell script: read the JSON, if
cell_delta_v > 50 mV or any
error_bitmask bit flips, fire a Telegram message. No
cloud account, no app subscription, no proprietary integration —
one local file, parsed.
The full 300-byte frame travels with every record as
raw_frame_hex. When JK pushes new firmware that nudges
a field offset, re-parse historical logs locally with the next
version of jkbms-poll. Your data is never trapped behind a
schema change.