JK BMS Web Gateway
Súkromný projekt (LTSolutions), ktorý sprístupňuje JK BMS riadiace jednotky pre LiFePO4 batérie vo fotovoltaike bez telefónnej BLE aplikácie ani PC nástroja výrobcu. ESP32 gateway číta batériu cez RS485 a počúva CAN vysielanie meniča, a vystavuje to ako JSON REST API a webový dashboard priamo vo vašej WiFi sieti.
Výrobca BMS jednotiek: jkbms.com / jk-bms.com. Tento projekt s výrobcom nijako nesúvisí.
Potrebný hardvér
Gateway potrebuje ESP32 dosku, RS485 prevodník a (voliteľne) CAN prevodník, zapojené podľa tabuliek nižšie. Presné čísla pinov sú definované v src/main.cpp firmvéru — ak zapojíte inak, zmeňte ich tam.
Čo potrebujete
- Ľubovoľná ESP32 vývojová doska (napr. ESP32-WROOM-32, 38-pin DevKit)
- MAX485 / MAX3485 TTL↔RS485 prevodník (3.3 V verzia, s DE a RE spojenými na jeden pin)
- SN65HVD230 (3.3 V) CAN prevodník — potrebný len ak chcete čítať aj CAN vysielanie meniča
- RJ45 kábel do kombinovaného 485/CAN portu BMS
- 5V napájací zdroj pre ESP32 dosku
Voliteľne: pripravená KiCad doska (carrier PCB), ktorá spája ESP32 DevKit, oba prevodníky a dva RJ45 konektory na jednej doštičke — zdrojové súbory na GitHube.
Zapojenie MAX485 (RS485) → ESP32
| Pin MAX485 | Pin ESP32 | Poznámka |
|---|---|---|
| VCC | 3.3V | Oba prevodníky sú natívne 3.3V súčiastky — nenapájajte ich z 5V, mohli by preťažiť RX pin ESP32. |
| GND | GND | |
| DI (driver in) | GPIO17 (TX2) | |
| RO (receiver out) | GPIO16 (RX2) | |
| DE + RE (spojené) | GPIO4 | HIGH = vysielanie, LOW = príjem |
| A | BMS RS485-A (RJ45 pin 2/7) | |
| B | BMS RS485-B (RJ45 pin 1/8) | |
Zapojenie SN65HVD230 (CAN) → ESP32
| Pin SN65HVD230 | Pin ESP32 | Poznámka |
|---|---|---|
| 3 VCC | 3.3V | Oba prevodníky sú natívne 3.3V súčiastky — nenapájajte ich z 5V, mohli by preťažiť RX pin ESP32. |
| 2 GND | GND | |
| 1 D (TXD) | GPIO25 | CAN_TX_PIN |
| 4 R (RXD) | GPIO26 | CAN_RX_PIN |
| 8 Rs | 10 kΩ → GND | |
| 5 Vref | — | |
| 7 CANH / 6 CANL | BMS CAN-H / CAN-L | |
Kombinovaný port BMS 485/CAN (RJ45)
JK-PB2A16S20P má jeden kombinovaný RJ45 port "485/CAN", ktorý nesie obe zbernice naraz:
| Pin RJ45 | Signál |
|---|---|
| 1, 8 | RS485-B |
| 2, 7 | RS485-A |
| 3 | NC |
| 4 | CAN-H |
| 5 | CAN-L |
| 6 | GND |
Dôležité poznámky
- Firmvér počúva CAN zbernicu iba pasívne (listen-only) — nikdy nič nevysiela, takže je bezpečné pripojiť sa aj na živú zbernicu medzi BMS a meničom.
- 120 Ω terminačný odpor na CAN pridajte iba ak je ESP32 skutočným koncom zbernice. Ak je len odbočkou na zbernici, ktorú už terminuje BMS a menič, odpor nepridávajte.
- Prenosová rýchlosť CAN je predvolene 500 kbit/s (JK predvolené) a dá sa zmeniť za behu na karte CAN v dashboarde alebo cez
POST /api/can/config.
Kontaktný formulár
Nastavenie JK BMS-Updatera
Ponúkaná verzia: R1-V1.00e
esptool.py.
Súbory z priečinka nižšie naflashujte príkazom:
esptool.py --chip esp32 --port PORT write_flash \
0x1000 bootloader.bin \
0x8000 partitions.bin \
0xe000 boot_app0.bin \
0x10000 firmware.bin \
0x290000 littlefs.bin
bootloader.bin · partitions.bin · boot_app0.bin · firmware.bin · littlefs.bin
REST API
Všetky odpovede sú JSON. Príklad: curl http://jkbms.local/api/realtime
| Metóda | Cesta | Popis |
|---|---|---|
| GET | /api/info |
Stav brány (IP, uptime, posledné čítanie) |
| GET | /api/realtime[?addr=N] |
Napätia článkov, V/I/P batérie, SOC, teploty, alarmy |
| GET | /api/settings[?addr=N] |
Aktuálne hodnoty ochranných parametrov |
| POST | /api/settings[?addr=N] |
Telo: {"nazovPola": hodnota, ...} — zapíše zmenené polia |
| GET | /api/scan |
Preskenuje adresy 0–15, vráti ktoré odpovedajú |
| GET | /api/can |
Dekódovaný súhrn z CAN vysielania meniča (SOC, V/I/T, limity, príznaky, výrobca) |
| GET | /api/can/raw[?n=20] |
Posledné surové CAN rámce (id, dlc, data[], vek v ms) |
| GET / POST | /api/can/config |
Nastavenie CAN — prenosová rýchlosť, autoscan, profil (ukladá sa do NVS) |
| GET | /api/debug/read?reg=0xHEX&count=N[&addr=N] |
Surové čítanie Modbus registrov |
| POST | /api/debug/verifywrite?reg=0xHEX[&addr=N] |
Prečíta register, zapíše rovnakú hodnotu späť, znova prečíta — overí zápisovú cestu bez zmeny |
Ukážkový C/C++ konzolový klient
Minimálny klient — jeden HTTP GET, jeden JSON parse, vypíše napätie/prúd/SOC batérie. Stiahnuť jkbms_client_example.cpp
// JK BMS Web Gateway — minimal REST API console client.
//
// Fetches GET /api/realtime from the gateway and prints pack voltage,
// current, and state of charge. Demonstrates the smallest useful client:
// one HTTP GET, one JSON parse. See the full endpoint list and JSON field
// reference on the site's #api section.
//
// Build (Linux/macOS, needs libcurl + nlohmann/json):
// g++ -std=c++17 jkbms_client_example.cpp -lcurl -o jkbms_client_example
// Run:
// ./jkbms_client_example http://jkbms.local
#include <curl/curl.h>
#include <nlohmann/json.hpp>
#include <cstdio>
#include <cstdlib>
#include <string>
using nlohmann::json;
// libcurl calls this once per received chunk of the HTTP response body;
// appending to a std::string is the standard way to buffer a small response.
static size_t appendToBuffer(char *data, size_t size, size_t count, void *userData) {
auto *buffer = static_cast<std::string *>(userData);
buffer->append(data, size * count);
return size * count;
}
// Performs one blocking HTTP GET and returns the response body.
// Throws std::runtime_error if the transfer itself fails (not on HTTP
// error status — the caller is expected to check the JSON's own "error" field).
static std::string httpGet(const std::string &url) {
CURL *curl = curl_easy_init();
if (!curl) {
throw std::runtime_error("curl_easy_init failed");
}
std::string body;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, appendToBuffer);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);
CURLcode result = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (result != CURLE_OK) {
throw std::runtime_error(curl_easy_strerror(result));
}
return body;
}
int main(int argc, char *argv[]) {
// Default to the gateway's mDNS name; override with a bare IP if mDNS
// isn't reachable on your network (e.g. "http://192.168.1.50").
std::string baseUrl = (argc > 1) ? argv[1] : "http://jkbms.local";
std::string body;
try {
body = httpGet(baseUrl + "/api/realtime");
} catch (const std::exception &ex) {
std::fprintf(stderr, "request failed: %s\n", ex.what());
return 1;
}
json realtime = json::parse(body, /*cb*/ nullptr, /*allow_exceptions*/ false);
if (realtime.is_discarded()) {
std::fprintf(stderr, "invalid JSON response: %s\n", body.c_str());
return 1;
}
if (realtime.contains("error")) {
std::fprintf(stderr, "gateway error: %s\n", realtime["error"].get<std::string>().c_str());
return 1;
}
std::printf("Pack voltage: %.2f V\n", realtime.value("totalVoltage", 0.0));
std::printf("Current: %.2f A\n", realtime.value("current", 0.0));
std::printf("SOC: %d %%\n", realtime.value("soc", 0));
return 0;
}