Reads JK-BMS data via RS485/Ethernet directly into ioBroker using the JavaScript adapter — no MQTT broker, no additional drivers, no extra services needed.
Tested with: JK-BMS PB2A16S20P v19 (up to 16 cells, up to 4 parallel packs), connected to RS485-2 (right port) at Master BMS #0
The JK-BMS broadcasts binary frames in the 55AA protocol roughly every 200 ms automatically — no polling required. The script opens a TCP connection to a RS485/Ethernet converter, listens passively, validates each frame (CRC16 Modbus), and writes the parsed values into the ioBroker state tree every 10 seconds.
JK-BMS ──RS485──► Ethernet Converter ──TCP──► ioBroker JavaScript Adapter
115200 Bd transparent 55AA passive listener
Three frame types are broadcast:
| Register | Type | Content |
|---|---|---|
0x1620 |
DYNAMIC | Cell voltages, current, SOC, temperatures — parsed by this script |
0x161C |
STATIC | Hardware/software version, serial number |
0x161E |
SETUP | Protection thresholds, current limits |
- JK-BMS PB2A16S20P with RS485-2 port
- RS485-to-Ethernet converter, e.g.:
- Waveshare USR-TCP232-306 / RS485 TO ETH (B)
- Ebyte MA01-XXAC or similar
- LAN connection from converter to ioBroker host
Example: Waveshare RS485 TO ETH (B) — web interface at http://<device-ip>
Network
| Field | Value |
|---|---|
| Work Mode | TCP Server |
| Device Port | 5000 |
| IP mode | DHCP (or static) |
| Destination Port | 0 |
Serial
| Field | Value |
|---|---|
| Baud Rate | 115200 |
| Databits | 8 |
| Parity | None |
| Stopbits | 1 |
| Flow control | None |
Advanced
| Field | Value |
|---|---|
| No-Data-Restart | Disable |
| Enable Multi-host | Yes |
Connect to the Master BMS (Device ID 0):
| Switch | Position | Meaning |
|---|---|---|
| 1 | OFF | RS485 mode active |
| 2 | OFF | |
| 3 | OFF | |
| 4 | OFF | Device ID = 0 (Master) |
Connect to the RS485-2 (right) port on the Master BMS.
- In ioBroker, install the JavaScript adapter (if not already installed).
- Open the JavaScript adapter → + Add script.
- Copy the contents of
jkbms_55aa.jsinto the editor. - Adjust the
CONFIGblock at the top of the script (see below). - Save and start the script.
The script creates all states automatically on first start. Check the ioBroker log for connection status.
All settings are in the CONFIG block at the top of the script:
const CONFIG = {
host: '192.168.7.109', // IP of the RS485/Ethernet converter
port: 5000, // TCP port (default: 5000 or 8899)
statePrefix: 'javascript.0.jkbms',
reconnectMs: 5000, // Reconnect delay in ms
maxPackId: 3, // Highest pack ID (0 = 1 pack, 3 = up to 4 packs)
cacheMaxAgeS: 60, // Pack goes offline after N seconds without a frame
writeIntervalMs: 5000, // Fast write interval: voltages, current, SOC, temps (5 s)
slowIntervalMs: 300000, // Slow write interval: cell resistances, SOH, cycles (5 min)
};| Parameter | Default | Description |
|---|---|---|
host |
192.168.7.109 |
IP address of the RS485/Ethernet converter |
port |
5000 |
TCP port of the converter |
statePrefix |
javascript.0.jkbms |
ioBroker state path prefix |
reconnectMs |
5000 |
Delay before reconnect attempt (ms) |
maxPackId |
3 |
Highest expected pack ID (0-based). Set to 0 for a single BMS. |
cacheMaxAgeS |
60 |
Seconds after which a pack with no new frames is marked offline |
writeIntervalMs |
5000 |
Write interval for fast states (voltages, current, SOC, temps) |
slowIntervalMs |
300000 |
Write interval for slow states (cell resistances, SOH, cycle count, runtime) |
writeBudget |
80 |
Max. setState calls per write cycle (overflow protection, see below) |
The ioBroker JavaScript adapter throttles scripts that exceed ~1000 setState calls per minute. This script avoids that with two mechanisms:
Change-only: setState is only called when a value actually changed. In steady state (float charging, stable voltage) nearly zero writes occur per cycle.
Per-cycle budget (writeBudget): Even if many values change at once (startup, alarm storm), at most writeBudget setState calls are made per cycle. Values that exceed the budget are deferred to the next cycle — they remain "pending" in the change cache until written.
| Scenario | setState calls |
|---|---|
| Steady state | ~0–5 per 5 s cycle |
| Worst case (all values change) | 80 per cycle × 12 cycles/min = 960/min (incl. last_update) |
| Startup (4 packs, ~168 fast states) | 80/cycle → all written within ~15 s |
States per pack:
| Category | Count | Interval |
|---|---|---|
| Voltage, current, power, SOC, remaining capacity | 5 | fast (5 s) |
| Balance current, MOS flags, alarm bits, status, wire alarm | 7 | fast (5 s) |
| Charger, heater, fault count | 3 | fast (5 s) |
| Temperatures (MOS + up to 5 NTC) | 2–6 | fast (5 s) |
| Cell stats (max, min, avg, diff) | 4 | fast (5 s) |
| 16× cell voltage (cell_01_mv … cell_16_mv) | 16 | fast (5 s) |
| Fast total per pack | ~37–41 | |
| SOH, full capacity, cycle count, cycle capacity, runtime | 5 | slow (5 min) |
| 16× cell resistance (cell_01_mohm … cell_16_mohm) | 16 | slow (5 min) |
| Slow total per pack | 21 | |
| Total per pack | ~58–62 |
For 4 packs + aggregate totals: ~168 fast states + 84 slow states = ~252 states total.
All values are written under javascript.0.jkbms (configurable via statePrefix).
| State | Unit | Description |
|---|---|---|
voltage |
V | Total pack voltage |
current |
A | Charge (+) / discharge (−) current |
power |
W | Power |
soc |
% | State of charge |
soh |
% | State of health |
remain_cap |
Ah | Remaining capacity |
full_cap |
Ah | Full charge capacity |
cycle_count |
— | Total charge cycles |
cycle_cap |
Ah | Cumulative cycled capacity |
balance_current |
A | Active balance current |
temp_mos |
°C | MOSFET temperature |
temp_bat1 … temp_bat5 |
°C | Battery temperature sensors (NTC 1–5) |
charge_mos |
bool | Charge MOSFET enabled |
discharge_mos |
bool | Discharge MOSFET enabled |
balance_mos |
bool | Balancing active |
heating_state |
bool | Heater active |
charger_plugged |
bool | Charger connected |
alarm_bits |
— | 32-bit alarm flags (see below) |
wire_alarm |
— | Wire connection alarm bits |
battery_state |
— | Raw status byte |
fault_count |
— | Cumulative fault count |
total_runtime_h |
h | Total runtime in hours |
last_update |
ISO | Timestamp of last write |
cell_01_mv … cell_16_mv |
mV | Individual cell voltages |
cell_01_mohm … cell_16_mohm |
mΩ | Individual cell resistances |
cell_max_mv |
mV | Highest cell voltage |
cell_min_mv |
mV | Lowest cell voltage |
cell_avg_mv |
mV | Average cell voltage |
cell_diff_mv |
mV | Spread (max − min) |
| State | Unit | Description |
|---|---|---|
voltage_avg |
V | Average voltage across all packs |
current |
A | Total current (sum of all packs) |
power |
W | Total power (sum of all packs) |
soc |
% | Weighted SOC (remainCap / fullCap) |
remain_cap |
Ah | Total remaining capacity |
full_cap |
Ah | Total full capacity |
active_packs |
— | Number of packs currently online |
alarm_bits is a 32-bit field. Bit 0 = LSB.
| Bit | Meaning |
|---|---|
| 0 | Cell overvoltage |
| 1 | Cell undervoltage |
| 2 | Pack overvoltage |
| 3 | Pack undervoltage |
| 4 | Charge overcurrent |
| 5 | Discharge overcurrent |
| 6 | Charge overtemperature |
| 7 | Discharge overtemperature |
| 8 | Charge undertemperature |
| 9 | Discharge undertemperature |
| 10 | MOS overtemperature |
| 11 | Environment overtemperature |
| 12 | Environment undertemperature |
| 13 | Cell voltage difference too large |
| 14 | SOC low alarm |
| 15 | SOC high alarm |
| 16–23 | Sensor / hardware fault flags |
To check a specific alarm in ioBroker scripts:
const bits = getState('javascript.0.jkbms.pack0.alarm_bits').val;
if (bits & (1 << 0)) { /* cell overvoltage */ }
if (bits & (1 << 1)) { /* cell undervoltage */ }Dieses Script liest JK-BMS-Daten per RS485/Ethernet-Konverter passiv aus und schreibt sie direkt in den ioBroker-Objektbaum — über den JavaScript-Adapter, ohne MQTT, ohne externe Dienste.
Der JK-BMS sendet alle ~200 ms automatisch binäre 55AA-Frames. Das Script verbindet sich per TCP, parst die Frames kontinuierlich im Puffer und validiert mit CRC16-Modbus. Geschrieben wird nur bei Wertänderung (change-only), Messwerte alle 5 Sekunden, Zellwiderstände und Langzeitwerte alle 5 Minuten. Bis zu 4 parallele Packs (Master-Slave-Konfiguration) werden unterstützt und zu Gesamtwerten aggregiert.
MIT — free to use and adapt.
