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

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 MAX485Pin ESP32Poznámka
VCC3.3VOba prevodníky sú natívne 3.3V súčiastky — nenapájajte ich z 5V, mohli by preťažiť RX pin ESP32.
GNDGND
DI (driver in)GPIO17 (TX2)
RO (receiver out)GPIO16 (RX2)
DE + RE (spojené)GPIO4HIGH = vysielanie, LOW = príjem
ABMS RS485-A (RJ45 pin 2/7)
BBMS RS485-B (RJ45 pin 1/8)

Zapojenie SN65HVD230 (CAN) → ESP32

Pin SN65HVD230Pin ESP32Poznámka
3 VCC3.3VOba prevodníky sú natívne 3.3V súčiastky — nenapájajte ich z 5V, mohli by preťažiť RX pin ESP32.
2 GNDGND
1 D (TXD)GPIO25CAN_TX_PIN
4 R (RXD)GPIO26CAN_RX_PIN
8 Rs10 kΩ → GND
5 Vref
7 CANH / 6 CANLBMS 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 RJ45Signál
1, 8RS485-B
2, 7RS485-A
3NC
4CAN-H
5CAN-L
6GND

Dôležité poznámky

Kontaktný formulár

Nastavenie JK BMS-Updatera

⚠️ Nezodpovedáme za prípadné škody pri nesprávnom používaní!

Ponúkaná verzia: R1-V1.00e

Váš prehliadač nepodporuje Web Serial API potrebné na flashovanie priamo tu. Funguje to v Chrome, Edge alebo Opera na počítači. Stiahnite si binárky a naflashujte ich ručne pomocou esptool.py.
Táto stránka musí bežať cez zabezpečené HTTPS pripojenie, aby flashovanie z prehliadača fungovalo.

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ódaCestaPopis
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;
}