---
title: "esp32poeLarsBatMon"
url: "https://maker.wiznet.io/TheoIm/projects/esp32poelarsbatmon/"
markdown_url: "https://maker.wiznet.io/TheoIm/projects/esp32poelarsbatmon/md"
type: "UCC: User Created Content"
author: "silly82"
author_url: "https://github.com/silly82/esp32poeLarsBatMon"
editor: "WIZnet"
editor_url: "https://maker.wiznet.io/"
original_author: "silly82"
original_url: "https://github.com/silly82/esp32poeLarsBatMon"
published: "2026-09-02"
updated: "2026-10-08"
language: "en"
hardware: ["WIZnet W5500"]
likes: 0
views: 184
comments: 0
source: "WIZnet Makers (https://maker.wiznet.io/)"
---

# esp32poeLarsBatMon

> An ESP32-S3 reads a LiFePO4 BMS over BLE and publishes it as MQTT over a W5500 PoE link, where Node-RED turns it into a Victron virtual battery.

Original author: silly82 (source: https://github.com/silly82/esp32poeLarsBatMon)

## Components

- **WIZnet W5500** x 1 ([docs](https://docs.wiznet.io/Product/Chip/Ethernet/W5500))

WIZnet parts: W5500 ([Datasheet](https://docs.wiznet.io/Product/Chip/Ethernet/W5500/datasheet?utm_source=maker&utm_medium=project&utm_campaign=w5500), [product hub](https://maker.wiznet.io/products/w5500/))

## Article

## Overview

**esp32poeLarsBatMon** is an open-source battery monitor for a boat named *Lars*. An ESP32-S3 reads cell-level data out of a LiFePO4 battery's BMS over Bluetooth LE, publishes it as MQTT over a PoE Ethernet link carried by a WIZnet W5500, and a Node-RED flow on a Victron Cerbo GX turns those messages into a virtual battery service on the Victron D-Bus — so the pack appears in VRM and on the GX display alongside the rest of the vessel's energy system.

The repository is MIT-licensed, and the author reports the chain working end to end with live readings cross-checked against both the Victron display and the battery vendor's own app.

이 프로젝트는 보트 *Lars*에 설치된 LiFePO4 배터리의 BMS를 Bluetooth LE로 읽어, PoE Ethernet(W5500)을 통해 MQTT로 내보내고, Victron Cerbo GX의 Node-RED가 이를 Victron D-Bus의 가상 배터리 서비스로 변환하는 오픈소스 배터리 모니터입니다. 변환된 값은 VRM과 GX 디스플레이에 실제 배터리처럼 표시됩니다.

## Purpose

The goal is to bring a closed, Bluetooth-only battery into an established marine energy-monitoring platform over a wired, power-over-the-same-cable link — and to write down the protocol work so the next person does not have to repeat it.

**Core Objectives**

- Read voltage, current, SoC, SoH, capacity, temperature and per-cell voltages from a Redodo LiFePO4 BMS over BLE GATT

- Publish them as retained MQTT topics over PoE Ethernet, using the W5500 as the wired interface

- Map the values onto Victron D-Bus paths through Node-RED so the pack becomes a first-class device in VRM

- Document the BMS frame format, which is not the common JBD command set

목표는 Bluetooth로만 접근 가능한 폐쇄형 배터리를, 전원과 데이터를 한 가닥으로 받는 유선 링크를 통해 기존 선박용 에너지 모니터링 플랫폼에 편입시키는 것입니다. 그리고 그 과정에서 밝혀낸 프로토콜을 문서로 남겨 다음 사람이 같은 작업을 반복하지 않게 하는 것입니다.

## System Architecture

```
Redodo LiFePO4 12.8 V / 100 Ah (RH190)
   |  BLE GATT   service 0xFFE0 - notify 0xFFE1 - write 0xFFE2
   v
ESP32-S3  (Waveshare ESP32-S3-ETH)
   |  W5500 over SPI   CLK 13 - MISO 12 - MOSI 11 - CS 14 - IRQ 10 - RST 9
   |  802.3af PoE on the same cable
   v  MQTT
Victron Cerbo / Venus GX   broker 192.168.24.213:1883
   |  Node-RED flow  (node-red/lifepo01-victron-flow.json)
   v
Victron D-Bus  ->  VRM / GX display
```

| Component | Description |
| --- | --- |
| Battery / BMS | Redodo Power LiFePO4 12.8 V / 100 Ah, model RH190, 4S pack. Exposes a UART-bridge style GATT layout at 0xFFE0 with notify on 0xFFE1 and write on 0xFFE2. |
| Gateway | Waveshare ESP32-S3-ETH (ESP32-S3R8, 16 MB flash, 8 MB octal PSRAM). Runs the BLE client and the MQTT publisher concurrently. |
| Network | WIZnet W5500 over SPI, with an optional 802.3af PoE module on the same board, so one cable carries both power and data to the node. |
| Broker | The Victron Cerbo/Venus GX's own MQTT broker at 192.168.24.213:1883, no authentication. |
| Integration | Node-RED on Venus OS Large. An importable flow feeds a `victron-virtual` node configured as a 100 Ah / 12 V battery device. |
| Data layer | Three retained topics: `LiFePo01/status`, `LiFePo01/battery`, `LiFePo01/battery/cells`. |

## Role and Application of the W5500

**Wired interface for a node whose radio is already busy.** The ESP32-S3's Bluetooth stack is fully occupied talking to the battery. The W5500 gives the node a second, independent path for its uplink, so BLE collection and MQTT publishing do not compete for the same radio.

**Power and data on one cable.** The board's optional 802.3af module means the monitor needs no separate supply — relevant on a boat, where every additional run of wire is a installation cost.

**How the firmware drives it.** Ethernet comes up through the ESP-IDF Ethernet layer:

```
#include <ETH.h>
#define ETH_PHY_TYPE ETH_PHY_W5500
#define ETH_PHY_ADDR 1
#define ETH_PHY_CS   14
#define ETH_PHY_IRQ  10
#define ETH_PHY_RST  9

ETH.begin(ETH_PHY_TYPE, ETH_PHY_ADDR, ETH_PHY_CS, ETH_PHY_IRQ, ETH_PHY_RST, SPI);
```

**What the W5500 does *not* do here.** This is the `esp_eth` path, so **TCP/IP is handled by lwIP on the ESP32 and the W5500 is used as MAC and PHY.** The chip's hardwired TCP/IP offload is not part of this design, and the project makes no claim that it is. Readers looking for an offload example should look elsewhere; readers looking for a reliable wired link on a board whose radio is committed elsewhere will find exactly that.

이 프로젝트에서 W5500은 **MAC/PHY 역할**을 합니다. 펌웨어가 `ETH.h`의 `ETH_PHY_W5500` 경로를 쓰기 때문에 TCP/IP 처리는 ESP32 쪽 lwIP가 담당하며, 칩의 하드웨어 TCP/IP 오프로드는 사용되지 않습니다. 대신 W5500이 제공하는 것은 **이미 BLE로 점유된 무선과 독립된 유선 경로**이고, 보드의 802.3af 모듈 덕분에 전원과 데이터가 한 가닥으로 들어옵니다.

## Key Features

| Feature | Description |
| --- | --- |
| BLE BMS client | Connects to the pack's GATT service, subscribes to notifications, and issues status queries. 배터리 BMS에 GATT로 접속해 notify를 구독하고 상태를 질의합니다. |
| Reverse-engineered frame format | Implements the Redodo/LiTime/PowerQueen command set, cross-checked against two existing open-source projects. 기존 오픈소스 2종과 교차 검증한 명령 포맷을 구현합니다. |
| Fragmented response reassembly | Notifications may arrive split; the reader detects a new frame and reassembles it. 단편화되어 도착하는 응답을 재조립합니다. |
| PoE Ethernet uplink | W5500 over SPI with an optional 802.3af module — one cable for power and data. 전원과 데이터를 한 가닥으로 받습니다. |
| Retained MQTT topics | Status heartbeat, battery summary, and per-cell voltages. 상태·요약·셀 전압을 각각 retained 토픽으로 발행합니다. |
| Victron D-Bus integration | Node-RED flow maps MQTT to Dc/0/Voltage, Soc, TimeToGo and friends, creating a virtual battery service. Node-RED가 MQTT를 Victron D-Bus 경로로 매핑합니다. |
| Secrets kept out of the tree | The battery MAC lives in a gitignored `include/secrets.h`. 배터리 MAC은 커밋되지 않습니다. |

## The part that took the work: identifying the BMS protocol

The GATT layout `0xFFE0/1/2/3` is a generic UART-bridge pattern shared by many unrelated BMS families, so having the right UUIDs was not enough. The author reports that the JBD/Xiaoxiang command set produced **zero response** despite a provably correct GATT connection — services and characteristics discovered, notify subscribed with peer ACK, writes ACKed.

A full GATT dump also surfaced an unrelated third-party service (`f000ffc0-0451-4000-b000-000000000000`, a TI CC254x-style base UUID, on what the Device Information Service reports as a Beken BLE SoC) that answers any input with a fixed handshake/error frame — a dead end.

What resolved it was identifying the battery brand from a photo of its label and searching for existing open-source reverse-engineering of that brand, rather than continuing to guess bytes. The implementation is cross-checked against:

- [rubenmuehlhans/litime-ble-hacs](https://github.com/rubenmuehlhans/litime-ble-hacs) — Home Assistant integration

- [va13ak/esp_redodo_bms](https://github.com/va13ak/esp_redodo_bms) — ESPHome, used as a cross-check

The command frame is 8 bytes, written to `0xFFE2`:

```
{0x00, 0x00, 0x04, 0x01, CMD, 0x55, 0xAA, CHECKSUM}
CHECKSUM = (0x04 + CMD) & 0xFF
CMD_QUERY_STATUS = 0x13
```

The status response notifies on `0xFFE1`, little-endian, **and may arrive fragmented**: a new frame starts when `byte[2] == 0x65`, and the reader reassembles until at least 104 bytes are in hand. Byte offsets and the remaining commands are listed in the header comment of `src/main.cpp`.

UUID만 맞춰서는 되지 않았습니다. 가장 널리 쓰이는 JBD 명령셋은 GATT 연결이 정상인데도 응답이 전혀 없었고, 결국 배터리 라벨 사진으로 제조사를 특정한 뒤 해당 브랜드의 오픈소스 분석 자료를 찾는 방식으로 해결됐습니다. 응답이 단편화되어 온다는 점과 그 재조립 규칙까지 문서에 남아 있습니다.

## A trap worth carrying away: the MQTT payload that failed silently

`#define MQTT_MAX_PACKET_SIZE` placed before `#include <PubSubClient.h>` only takes effect in the one translation unit that includes the header. PubSubClient's own source is compiled separately and **kept its 256-byte default buffer.**

The symptom was asymmetric, which is what made it hard to see: the small `LiFePo01/battery/cells` payload published fine, while the larger `LiFePo01/battery` payload — around 600 bytes once a `raw_hex` copy of the BMS response was included — failed every time. `publish()`'s return value was not being checked, so nothing announced the failure.

The fix was to stop relying on the compile-time define and call the runtime setters in `setup()`:

```
client.setBufferSize(2048);
client.setKeepAlive(60);
```

A second robustness fix is recorded as well: a nearby device flooding BLE advertisements (Apple-continuity-style spam) overloaded the scan callback and corrupted serial output, resolved with `scan->setDuplicateFilter(true)`.

컴파일 타임 `#define`은 헤더를 include한 하나의 translation unit에만 적용되므로, 별도로 컴파일되는 라이브러리 내부는 기본 256바이트 버퍼를 유지합니다. 그래서 **큰 페이로드만 조용히 실패하고 작은 것은 계속 성공하는** 증상이 나타났습니다. 런타임 설정 함수로 바꾸어 해결했습니다.

## Cross-checks the author published

Numbers were read at three independent points and compared. From the ESP32's own MQTT payload:

```
{"total_voltage_v":13.291,"current_a":-4.369,"power_w":-58.07,
 "soc_percent":92,"soh_percent":100,"remaining_capacity_ah":97.15,
 "full_charge_capacity_ah":105,"cell_temperature_c":31,
 "discharge_cycles":2}
```

Cell voltages read `[3.323, 3.323, 3.323, 3.322]` — a 4S pack, 4 × 3.2 V ≈ 12.8 V nominal, matching the nameplate.

On the Victron GX battery detail page the author reports SoC 90.0 %, 13.3 V, 4.4 A discharging, 58.0 W, 31.0 °C, min/max cell voltage 3.320 V / 3.322 V, and a remaining time of 21 h 45 min — against the ESP32's own 96.6 Ah / 4.4 A ≈ 21 h 57 min. That agreement is what confirmed `TimeToGo` was being computed and read correctly on both sides.

The battery vendor's app served as the third reference point (SOC 96 %, 13.3 V, −4.1 A, 100.8 Ah at 2026-08-15 20:40), consistent given the time elapsed between readings.

## Engineering Value and Source Limits

What the repository demonstrates is a complete path from a closed BLE battery to an established energy-monitoring platform, with the protocol work written down rather than left implicit in the code. For anyone facing a Redodo, LiTime or PowerQueen pack, the command frame and the reassembly rule alone are worth the visit.

What it does *not* establish, by the author's own account:

- **Ethernet reconnect has not been tested under real conditions.** MQTT and BLE reconnect have been; recovery after a cable or switch failure has not.

- **There is no watchdog.** `esp_task_wdt` is listed as a possible addition in case the firmware is ever stuck in a BLE wait state.

- **Protection and failure flags are published as raw bitmasks only** and are not decoded into Victron alarms; the known bit meanings are recorded as future work.

- **The virtual battery is monitoring-only.** DVCC charger control and the BMS's charge/discharge FET commands are documented as possible extensions, not implemented.

- No throughput or latency figures are published — and none are needed for a once-per-interval telemetry node.

- Part of the README documents a PlatformIO/esptool toolchain problem specific to the author's own machine rather than to the hardware.

저자가 직접 밝힌 미검증 범위입니다. MQTT와 BLE 재접속은 확인됐지만 **Ethernet 재접속은 실제 조건에서 테스트되지 않았고**, 워치독이 없으며, 보호/고장 플래그는 원시 비트마스크로만 발행되고 Victron 알람으로 변환되지 않습니다. 가상 배터리는 모니터링 전용이며 충전기 제어는 포함되지 않습니다.

## Market & Application Value

**Marine and off-grid energy systems.** Victron is the de-facto platform for boats, campers and off-grid cabins, and a great many drop-in LiFePO4 packs expose their data only over Bluetooth. This project is a worked example of closing that gap on the owner's side, without waiting for the battery vendor to ship an integration.

**Why wired matters here.** On a vessel the monitoring node and the GX may sit in different compartments, and the ESP32's radio is already committed to the BLE link. A PoE run gives the node power and a stable uplink in one cable — the kind of installation argument that applies equally to industrial cabinets and building automation.

**Reusable beyond this battery.** The BLE-to-MQTT-to-D-Bus shape is generic. Swap the BMS decoder and the same structure serves any Bluetooth-only sensor that needs to appear on a wired network as a proper device.

Victron은 보트·캠핑카·오프그리드 설비에서 사실상 표준 플랫폼이고, 많은 LiFePO4 배터리가 데이터를 Bluetooth로만 노출합니다. 이 프로젝트는 제조사의 공식 연동을 기다리지 않고 사용자 측에서 그 간극을 메운 사례이며, BLE → MQTT → D-Bus 구조 자체는 다른 Bluetooth 전용 센서에도 그대로 재사용할 수 있습니다.

## Source-Backed Summary

| Board | Waveshare ESP32-S3-ETH, W5500 over SPI, optional 802.3af PoE |
| --- | --- |
| Ethernet path | `ETH.h` / `ETH_PHY_W5500` → esp_eth → lwIP (not hardware TCP/IP offload) |
| Battery | Redodo Power LiFePO4 12.8 V / 100 Ah, model RH190, 4S |
| BLE | GATT 0xFFE0, notify 0xFFE1, write 0xFFE2, fragmented responses reassembled |
| Uplink | MQTT (PubSubClient) to the Venus GX broker, three retained topics |
| Integration | Node-RED flow → Victron D-Bus virtual battery, visible in VRM |
| Licence | MIT |
| Status | Author reports working end to end; Ethernet reconnect untested |

## FAQ

**Q. Does this use the W5500's hardwired TCP/IP?**
No. The firmware uses `ETH.h` with `ETH_PHY_W5500`, which is the ESP-IDF `esp_eth` path — lwIP on the ESP32 handles TCP/IP and the W5500 serves as MAC and PHY.

**Q. Why Bluetooth and Ethernet at the same time? Isn't one enough?**
They do different jobs here. BLE is the only way into the battery; Ethernet is how the result leaves the node. Both links are live simultaneously, which is also why a wired uplink is preferable to Wi-Fi on the same chip.

**Q. Will this work with any LiFePO4 battery?**
No. The command set is specific to the Redodo/LiTime/PowerQueen BMS family. The widely documented JBD/Xiaoxiang command set returned nothing on this pack, which is the problem the project had to solve first.

**Q. Can it control the charger or trip the battery?**
Not as published. The virtual battery is monitoring-only. The author documents the BMS's charge/discharge commands and Victron's DVCC paths as possible extensions, with an explicit note that FET control is safety-critical.

**Q. Is it safe to leave running unattended?**
The author does not claim so. There is no watchdog, and Ethernet reconnect after a cable or switch failure has not been tested — both are listed as open items.

Source: [github.com/silly82/esp32poeLarsBatMon](https://github.com/silly82/esp32poeLarsBatMon)

---

Source: https://maker.wiznet.io/TheoIm/projects/esp32poelarsbatmon/
