API & Dokumentation
Beide Gateways bieten im lokalen Netz eine einfache HTTP-JSON-API. Lesen geht ohne Anmeldung, jede Änderung (POST) braucht die PIN im Header X-PIN (ab Werk 666666). Wählen Sie ein Produkt, sehen Sie sich die Endpunkte an und starten Sie mit dem Beispielcode.
Alle Antworten sind JSON. Beispiel: curl http://jkbms.local/api/realtime
0 niemals verwenden. Eine Batterie mit Adresse 0 verhält sich von selbst als „Master” und durchsucht fortlaufend den gesamten Bus — das blockiert das Auslesen von ihr und den benachbarten Batterien, selbst bei korrekter Verkabelung, Terminierung und Erdung. Stellen Sie die Adresse jeder Batterie über die DIP-Schalter (oder die Hersteller-App) auf 1-15 ein.| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /api/info |
Gateway-Status (IP, Laufzeit, letzte Abfrage) |
| GET | /api/realtime[?addr=N] |
Zellspannungen, Pack V/I/P, SOC, Temperaturen, Alarme |
| GET | /api/settings[?addr=N] |
Aktuelle Werte der Schutzparameter |
| POST | /api/settings[?addr=N] |
Body: {"feldname": wert, ...} — schreibt geänderte Felder |
| GET | /api/deviceinfo[?addr=N] |
Anbieter-ID, Seriennummer, HW/SW-Version, Einschaltzähler (pro Adresse zwischengespeichert) |
| GET | /api/scan |
Fragt die Adressen 0–15 ab und meldet, welche antworten |
| GET | /api/can |
Dekodierte CAN-Wechselrichter-Übertragung (SOC, Pack V/I/T, Lade-/Entladegrenzwerte, Flags, Hersteller) |
| GET | /api/can/raw[?n=20] |
Zuletzt empfangene rohe CAN-Rahmen (id, dlc, data[], Alter in ms) |
| GET / POST | /api/can/config |
CAN-Einstellungen — Bitrate, Autoscan, Profil (in NVS gespeichert) |
| GET / POST | /api/server-config |
Webserver-Einstellungen — RS485-Lese-Timing (Verzögerung Schreiben→Lesen, Abfrageintervall, Antwort-Timeout) und optionale Hintergrundabfragen (in NVS gespeichert, sofort angewendet) |
| GET | /api/logs |
Protokollverlauf seit dem letzten Start (Abfrageergebnisse, CAN-/WLAN-/Lizenzereignisse) — nur im Speicher, setzt sich beim Neustart zurück |
| GET | /api/history[?minutes=N] |
Minutenmittelwerte der überwachten Batterie (Spannung, Strom, SOC) der letzten N Minuten, bis zu 24 h — die Daten hinter dem Dashboard-Diagramm |
| GET | /api/debug/read?reg=0xHEX&count=N[&addr=N] |
Rohes Lesen von Modbus-Haltregistern |
| POST | /api/debug/verifywrite?reg=0xHEX[&addr=N] |
Liest ein Register, schreibt denselben Wert zurück, liest erneut — belegt, dass der Schreibpfad das Register erreicht, ohne etwas zu ändern |
| POST | /api/wifi/reset |
Öffnet das WLAN-Einrichtungsportal erneut, damit das Netzwerk geändert werden kann |
| POST | /api/pin |
Ändert die Änderungs-PIN, Body {"newPin":"1234"} (4–8 Ziffern). Die aktuelle PIN steht im Header X-PIN. Abschalten lässt sie sich nicht. |
| GET | /api/firmware/check |
Das Gateway fragt bei jkbms.sk nach, ob eine neuere Firmware-Version existiert |
| POST | /api/firmware/update |
Installiert das neueste signierte Release von jkbms.sk (PIN und verifizierte Lizenz); das Gateway startet nach 1–2 Minuten in die neue Version neu |
Beispielcode
Jedes Beispiel macht ein GET /api/realtime und gibt Spannung, Strom und SOC aus. Die Gateway-Adresse wird als Parameter übergeben, Standard ist der mDNS-Name.
curl + jq (Shell)
jkbms_realtime.sh herunterladen
#!/usr/bin/env bash
# JK BMS Web Gateway — read live pack data with curl + jq.
#
# One HTTP GET, no authentication; the gateway answers JSON, or
# {"error": "..."} when the BMS did not respond.
#
# Usage: ./jkbms_realtime.sh [http://jkbms.local]
# (use the gateway's IP, e.g. http://192.168.1.50, if mDNS is not reachable)
set -euo pipefail
BASE_URL="${1:-http://jkbms.local}"
curl -sS --max-time 5 "$BASE_URL/api/realtime" | jq -r '
if .error then error("gateway error: " + .error) else
"Pack voltage: \(.totalVoltage) V\n" +
"Current: \(.current) A\n" +
"SOC: \(.soc) %"
end'
Python 3 (ohne Abhängigkeiten)
jkbms_realtime.py herunterladen
#!/usr/bin/env python3
"""JK BMS Web Gateway — read live pack data. Standard library only, no pip installs.
Usage: python3 jkbms_realtime.py [http://jkbms.local]
(use the gateway's IP, e.g. http://192.168.1.50, if mDNS is not reachable)
"""
import json
import sys
import urllib.request
def fetch_realtime(base_url: str) -> dict:
"""GET /api/realtime and return the decoded JSON.
Raises on network errors and when the gateway reports {"error": ...}
(BMS not answering, wrong address, ...).
"""
with urllib.request.urlopen(f"{base_url}/api/realtime", timeout=5) as response:
data = json.load(response)
if "error" in data:
raise RuntimeError(f"gateway error: {data['error']}")
return data
def main() -> int:
"""Prints voltage, current, SOC and the cell spread; returns the process exit code."""
base_url = sys.argv[1] if len(sys.argv) > 1 else "http://jkbms.local"
try:
realtime = fetch_realtime(base_url)
except Exception as ex: # network, JSON or gateway error: one line, non-zero exit
print(f"request failed: {ex}", file=sys.stderr)
return 1
print(f"Pack voltage: {realtime['totalVoltage']:.2f} V")
print(f"Current: {realtime['current']:.2f} A (positive = charging)")
print(f"SOC: {realtime['soc']} %")
print(f"Cells: {realtime['minCellVoltage']:.3f} .. {realtime['maxCellVoltage']:.3f} V")
return 0
if __name__ == "__main__":
sys.exit(main())
JavaScript (Node.js oder Browser)
jkbms_realtime.js herunterladen
// JK BMS Web Gateway — read live pack data with fetch().
//
// Runs as-is on Node.js 18+: node jkbms_realtime.js [http://jkbms.local]
// The gateway sends "Access-Control-Allow-Origin: *", so fetchRealtime()
// works unchanged inside a web page too (drop the process.* lines there).
const baseUrl = process.argv[2] ?? "http://jkbms.local";
// One GET, no authentication; rejects on network errors and on {"error": ...}
async function fetchRealtime(url) {
const response = await fetch(`${url}/api/realtime`, { signal: AbortSignal.timeout(5000) });
const data = await response.json();
if (data.error) {
throw new Error(`gateway error: ${data.error}`);
}
return data;
}
fetchRealtime(baseUrl)
.then((realtime) => {
console.log(`Pack voltage: ${realtime.totalVoltage.toFixed(2)} V`);
console.log(`Current: ${realtime.current.toFixed(2)} A`);
console.log(`SOC: ${realtime.soc} %`);
})
.catch((error) => {
console.error(`request failed: ${error.message}`);
process.exitCode = 1;
});
C/C++-Konsolenclient (libcurl + nlohmann/json)
jkbms_client_example.cpp herunterladen
// 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 documentation page (/dokumentacia).
//
// 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;
}
Alle Antworten sind JSON. Beispiel: curl http://deye.local/api/realtime
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /api/info |
Gateway-Status: Firmware-Version, IP/SSID, Laufzeit, letztes Abfrageergebnis, NTP-Status. |
| GET | /api/realtime |
Live-Wechselrichterdaten aus dem Abfrage-Cache (Betriebszustand, Relais, Leistung, Batterie, Energie, Fehler). |
| GET | /api/settings |
Aktuelle Einstellungen (liest Register 60–347). |
| POST | /api/settings |
Body: {"feld": wert, ...} — schreibt eine oder mehrere Einstellungen, mit Rücklese-Prüfung. |
| GET | /api/deviceinfo |
Gerätetyp, Seriennummer, Firmware-Versionen, Nennleistung. |
| GET | /api/history?minutes=N |
24-Stunden-Verlauf von Minutenmittelwerten (PV, Last, Netz, Batterie, SOC) für die Dashboard-Diagramme; minutes = 1–1440. |
| GET / POST | /api/server-config |
Webserver-Einstellungen des Gateways: Abfrageintervall des Wechselrichters, optionale Wettervorhersage und Spot-Strompreise für das Dashboard. |
| GET | /api/logs |
Protokollverlauf seit dem letzten Start (Abfrageergebnisse des Wechselrichters) — nur im Speicher, setzt sich beim Neustart zurück. |
| GET | /api/debug/read?reg=N&count=N |
Rohes Modbus-Register-Lesen — beliebiges Register, Anzahl 1–100. |
| POST | /api/debug/verifywrite?reg=N |
Liest ein Register, schreibt denselben Wert zurück, liest erneut — zerstörungsfreie Prüfung des Schreibpfads. |
| POST | /api/time/sync |
Schreibt die NTP-Zeit des Gateways in die Wechselrichteruhr. |
| POST | /api/wifi/reset |
Öffnet das WLAN-Portal „DEYE-Setup“ erneut, damit das Netzwerk geändert werden kann. |
| POST | /api/pin |
Ändert die Änderungs-PIN, Body {"newPin":"1234"} (4–8 Ziffern). Die aktuelle PIN steht im Header X-PIN. Abschalten lässt sie sich nicht. |
| GET | /api/firmware/check |
Das Gateway fragt jkbms.sk, ob eine neuere DEYE-Firmware-Version existiert. |
| POST | /api/firmware/update |
Installiert das neueste signierte Release von jkbms.sk (PIN und verifizierte Lizenz); das Gateway startet nach 1–2 Minuten in die neue Version neu. |
Beispielcode
Jedes Beispiel macht ein GET /api/realtime und gibt Spannung, Strom und SOC aus. Die Gateway-Adresse wird als Parameter übergeben, Standard ist der mDNS-Name.
curl + jq (Shell)
deye_realtime.sh herunterladen
#!/usr/bin/env bash
# DEYE Web Gateway — read live inverter data with curl + jq.
#
# One HTTP GET, no authentication; the gateway answers JSON, or
# {"error": "..."} when the inverter did not respond over Modbus.
#
# Usage: ./deye_realtime.sh [http://deye.local]
# (use the gateway's IP if mDNS is not reachable)
set -euo pipefail
BASE_URL="${1:-http://deye.local}"
curl -sS --max-time 5 "$BASE_URL/api/realtime" | jq -r '
if .error then error("gateway error: " + .error) else
"State: \(.runStateText)\n" +
"Battery: \(.batteryVoltage) V \(.batteryCurrent) A SOC \(.batterySoc) %\n" +
"PV: \(.pvPowerTotal) W\n" +
"Load: \(.loadPowerTotal) W\n" +
"Grid: \(.gridPowerTotal) W (positive = importing)"
end'
Python 3 (ohne Abhängigkeiten)
deye_realtime.py herunterladen
#!/usr/bin/env python3
"""DEYE Web Gateway — read live inverter data. Standard library only, no pip installs.
Usage: python3 deye_realtime.py [http://deye.local]
(use the gateway's IP if mDNS is not reachable)
"""
import json
import sys
import urllib.request
def fetch_realtime(base_url: str) -> dict:
"""GET /api/realtime and return the decoded JSON.
Raises on network errors and when the gateway reports {"error": ...}
(inverter not answering over Modbus).
"""
with urllib.request.urlopen(f"{base_url}/api/realtime", timeout=5) as response:
data = json.load(response)
if "error" in data:
raise RuntimeError(f"gateway error: {data['error']}")
return data
def main() -> int:
"""Prints the inverter state and the battery / PV / load / grid power; returns the exit code."""
base_url = sys.argv[1] if len(sys.argv) > 1 else "http://deye.local"
try:
realtime = fetch_realtime(base_url)
except Exception as ex: # network, JSON or gateway error: one line, non-zero exit
print(f"request failed: {ex}", file=sys.stderr)
return 1
print(f"State: {realtime['runStateText']}")
print(f"Battery: {realtime['batteryVoltage']:.1f} V {realtime['batteryCurrent']:.1f} A SOC {realtime['batterySoc']} %")
print(f"PV: {realtime['pvPowerTotal']} W")
print(f"Load: {realtime['loadPowerTotal']} W")
print(f"Grid: {realtime['gridPowerTotal']} W (positive = importing)")
return 0
if __name__ == "__main__":
sys.exit(main())