View Categories

BMS LC30 – Complete Python Examples

10-minute read

BMS LC30 – Complete Python Examples #

This page provides ready-to-use Python examples for interacting with the BMS LC30. The examples below use the library requests and are based on the routes actually generated by the included Python plugin.

1. Prerequisites #

Install the required Python dependency:

pip install requests

In the following examples, the API is assumed to be accessible at the following address:

http://127.0.0.1:5000

2. Minimal API client #

The code below provides a reusable Python client for calling API endpoints.

import requests


class BmsApiClient:
    def __init__(self, base_url="http://127.0.0.1:5000", timeout=5):
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout

    def get(self, path, **kwargs):
        response = requests.get(
            f"{self.base_url}{path}",
            timeout=self.timeout,
            **kwargs
        )
        response.raise_for_status()
        return response.json()

    def post(self, path, payload=None, **kwargs):
        response = requests.post(
            f"{self.base_url}{path}",
            json=payload or {},
            timeout=self.timeout,
            **kwargs
        )
        response.raise_for_status()
        return response.json()


if __name__ == "__main__":
    api = BmsApiClient()
    print(api.get("/api/bms"))

3. View key data from the BMS #

The Road /api/bms provides consolidated data: voltage, current, SOC, alarms, charge status, and connection status.

from pprint import pprint
from time import sleep
import requests


BASE_URL = "http://127.0.0.1:5000"


def read_bms():
    r = requests.get(f"{BASE_URL}/api/bms", timeout=5)
    r.raise_for_status()
    return r.json()


if __name__ == "__main__":
    data = read_bms()
    pprint(data)

Continuous Monitoring Version #

import time
import requests


BASE_URL = "http://127.0.0.1:5000"


def read_bms():
    r = requests.get(f"{BASE_URL}/api/bms", timeout=5)
    r.raise_for_status()
    return r.json()


while True:
    try:
        data = read_bms()
        print(
            f"connected={data.get('connected')} | "
            f"soc={data.get('soc')}% | "
            f"voltage={data.get('voltage')}V | "
            f"current={data.get('current')}A | "
            f"temp={data.get('temperature')}°C | "
            f"alarms={data.get('alarms', [])}"
        )
    except Exception as exc:
        print("Erreur lecture BMS :", exc)

    time.sleep(2)

4. I²C Diagnostics #

The Road /api/bms/diag allows you to diagnose the bus: available buses, present addresses, target address, and reading of several basic registers.

import json
import requests


BASE_URL = "http://127.0.0.1:5000"


def run_diag():
    r = requests.get(f"{BASE_URL}/api/bms/diag", timeout=10)
    r.raise_for_status()
    return r.json()


if __name__ == "__main__":
    diag = run_diag()
    print(json.dumps(diag, indent=2, ensure_ascii=False))

Example of a simple check #

import requests


BASE_URL = "http://127.0.0.1:5000"


r = requests.get(f"{BASE_URL}/api/bms/diag", timeout=10)
r.raise_for_status()
diag = r.json()

if diag.get("connected"):
    print("BMS détecté sur", diag.get("target_addr"), "bus", diag.get("target_bus"))
else:
    print("BMS non détecté")
    print(diag)

5. View and edit advanced settings #

The plugin provides a route for reading and writing advanced configuration settings, including advanced mode, the SOC source, and calibration gains and offsets.

Reading configuration #

import json
import requests


BASE_URL = "http://127.0.0.1:5000"


r = requests.get(f"{BASE_URL}/api/bms/advanced-config", timeout=5)
r.raise_for_status()

print(json.dumps(r.json(), indent=2, ensure_ascii=False))

Configuration Update #

import requests


BASE_URL = "http://127.0.0.1:5000"

payload = {
    "advanced_mode": True,
    "soc_source": "bms",
    "full_cell_voltage": 4.2,
    "empty_cell_voltage": 3.2,
    "voltage_gain": 1.0,
    "voltage_offset": 0.0,
    "current_gain": 1.0,
    "current_offset": 0.0
}

r = requests.post(f"{BASE_URL}/api/bms/advanced-config", json=payload, timeout=5)
r.raise_for_status()

print(r.json())

6. Voltage and Current Calibration #

The plugin provides dedicated routes for voltage and current calibration, as well as a reset of the coefficients.

Voltage calibration #

import requests


BASE_URL = "http://127.0.0.1:5000"

payload = {
    "reference_voltage": 15.92
}

r = requests.post(f"{BASE_URL}/api/bms/calibration/voltage", json=payload, timeout=5)
r.raise_for_status()

print(r.json())

Current Calibration #

import requests


BASE_URL = "http://127.0.0.1:5000"

payload = {
    "reference_current": -1.48
}

r = requests.post(f"{BASE_URL}/api/bms/calibration/current", json=payload, timeout=5)
r.raise_for_status()

print(r.json())

Reset calibration #

import requests


BASE_URL = "http://127.0.0.1:5000"

r = requests.post(f"{BASE_URL}/api/bms/calibration/reset", json={}, timeout=5)
r.raise_for_status()

print(r.json())

7. Read a register directly #

The Road /api/bms/register/read allows you to read a register in word, byte or block. The plugin also supports signed values.

Battery voltage reading #

import requests


BASE_URL = "http://127.0.0.1:5000"

payload = {
    "register": "0x09",
    "mode": "word",
    "signed": False
}

r = requests.post(f"{BASE_URL}/api/bms/register/read", json=payload, timeout=5)
r.raise_for_status()

print(r.json())

Reading the signed current #

import requests


BASE_URL = "http://127.0.0.1:5000"

payload = {
    "register": "0x0A",
    "mode": "word",
    "signed": True
}

r = requests.post(f"{BASE_URL}/api/bms/register/read", json=payload, timeout=5)
r.raise_for_status()

print(r.json())

Reading a manufacturer block #

import requests


BASE_URL = "http://127.0.0.1:5000"

payload = {
    "register": "0x20",
    "mode": "block",
    "length": 20
}

r = requests.post(f"{BASE_URL}/api/bms/register/read", json=payload, timeout=5)
r.raise_for_status()

print(r.json())

8. Write a log #

Writing to a registry requires advanced mode and explicit confirmation. Some registries remain protected without force_unsafe=true.

import requests


BASE_URL = "http://127.0.0.1:5000"

payload = {
    "register": "0x17",
    "mode": "word",
    "value": 12,
    "confirm_write": True
}

r = requests.post(f"{BASE_URL}/api/bms/register/write", json=payload, timeout=5)
print(r.status_code)
print(r.json())
Warning: Reserve these operations for maintenance or controlled development scenarios.

9. Use register profiles #

The plugin includes default profiles such as sbs_monitoring and manufacturer_strings, as well as the ability to create custom profiles.

List profiles #

import json
import requests


BASE_URL = "http://127.0.0.1:5000"

r = requests.get(f"{BASE_URL}/api/bms/register/profiles", timeout=5)
r.raise_for_status()

print(json.dumps(r.json(), indent=2, ensure_ascii=False))

Apply a reading profile #

import json
import requests


BASE_URL = "http://127.0.0.1:5000"

payload = {
    "profile": "sbs_monitoring",
    "write_mode": False
}

r = requests.post(f"{BASE_URL}/api/bms/register/profile/apply", json=payload, timeout=10)
r.raise_for_status()

print(json.dumps(r.json(), indent=2, ensure_ascii=False))

Create a custom profile #

import requests


BASE_URL = "http://127.0.0.1:5000"

payload = {
    "name": "pack_basic",
    "label": "Pack basic",
    "description": "Lecture tension, courant et SOC",
    "entries": [
        {"register": "0x09", "mode": "word", "signed": False, "label": "Voltage"},
        {"register": "0x0A", "mode": "word", "signed": True, "label": "Current"},
        {"register": "0x0D", "mode": "word", "signed": False, "label": "RelativeSOC"}
    ]
}

r = requests.post(f"{BASE_URL}/api/bms/register/profiles", json=payload, timeout=5)
r.raise_for_status()

print(r.json())

10. Device and cell information #

Studio routes allow you to retrieve more detailed information, including device information, complete SBS registers, and cell voltages.

Device info #

import json
import requests


BASE_URL = "http://127.0.0.1:5000"

r = requests.get(f"{BASE_URL}/api/bms/studio/info", timeout=5)
r.raise_for_status()

print(json.dumps(r.json(), indent=2, ensure_ascii=False))

Reading cells #

import json
import requests


BASE_URL = "http://127.0.0.1:5000"

r = requests.get(f"{BASE_URL}/api/bms/studio/cells", timeout=5)
r.raise_for_status()

print(json.dumps(r.json(), indent=2, ensure_ascii=False))

Full SBS Reading #

import json
import requests


BASE_URL = "http://127.0.0.1:5000"

r = requests.get(f"{BASE_URL}/api/bms/studio/sbs", timeout=10)
r.raise_for_status()

print(json.dumps(r.json(), indent=2, ensure_ascii=False))

11. Reading statuses and flags #

The Road /api/bms/studio/status allows you to retrieve the various flag groups: safety, PF, operation, charging, and gauging.

import json
import requests


BASE_URL = "http://127.0.0.1:5000"

r = requests.get(f"{BASE_URL}/api/bms/studio/status", timeout=10)
r.raise_for_status()

status = r.json()
print(json.dumps(status, indent=2, ensure_ascii=False))

12. MAC Commands #

The plugin lets you view a list of available MAC commands and send one. Some commands are marked as dangerous and require confirmation.

List MAC commands #

import json
import requests


BASE_URL = "http://127.0.0.1:5000"

r = requests.get(f"{BASE_URL}/api/bms/studio/mac_commands", timeout=5)
r.raise_for_status()

print(json.dumps(r.json(), indent=2, ensure_ascii=False))

Send a MAC command #

import requests


BASE_URL = "http://127.0.0.1:5000"

payload = {
    "command": "0x0001",
    "confirm": False
}

r = requests.post(f"{BASE_URL}/api/bms/studio/mac", json=payload, timeout=5)
print(r.status_code)
print(r.json())

13. Data Flash #

The plugin provides support for searching, reading, and writing Data Flash parameters, as well as raw reading of a memory block.

List Data Flash classes #

import json
import requests


BASE_URL = "http://127.0.0.1:5000"

r = requests.get(f"{BASE_URL}/api/bms/studio/df_classes", timeout=5)
r.raise_for_status()

print(json.dumps(r.json(), indent=2, ensure_ascii=False))

Search for a Data Flash setting #

import json
import requests


BASE_URL = "http://127.0.0.1:5000"
query = "0x45CC"

r = requests.get(f"{BASE_URL}/api/bms/studio/df_search", params={"q": query}, timeout=5)
r.raise_for_status()

print(json.dumps(r.json(), indent=2, ensure_ascii=False))

Read a Data Flash class #

import json
import requests


BASE_URL = "http://127.0.0.1:5000"

payload = {
    "class": "gas_gauging"
}

r = requests.post(f"{BASE_URL}/api/bms/studio/df_read", json=payload, timeout=15)
r.raise_for_status()

print(json.dumps(r.json(), indent=2, ensure_ascii=False))

Read a raw Data Flash block #

import json
import requests


BASE_URL = "http://127.0.0.1:5000"

payload = {
    "address": "0x4681"
}

r = requests.post(f"{BASE_URL}/api/bms/studio/df_raw", json=payload, timeout=10)
r.raise_for_status()

print(json.dumps(r.json(), indent=2, ensure_ascii=False))

Write a Data Flash parameter #

import requests


BASE_URL = "http://127.0.0.1:5000"

payload = {
    "address": "0x45CC",
    "value": 4200,
    "size": 2,
    "signed": False,
    "confirm": True
}

r = requests.post(f"{BASE_URL}/api/bms/studio/df_write", json=payload, timeout=10)
print(r.status_code)
print(r.json())
Note: Data Flash writing requires advanced mode and should be reserved for operations performed by experienced users.

14. Security Management #

The Road /api/bms/studio/security allows you to read the current mode and then send the actions seal, unseal or full_access.

Read the safety instructions #

import requests


BASE_URL = "http://127.0.0.1:5000"

r = requests.get(f"{BASE_URL}/api/bms/studio/security", timeout=5)
r.raise_for_status()

print(r.json())

Unseal #

import requests


BASE_URL = "http://127.0.0.1:5000"

payload = {
    "action": "unseal",
    "key1": "0x0414",
    "key2": "0x3672"
}

r = requests.post(f"{BASE_URL}/api/bms/studio/security", json=payload, timeout=5)
print(r.status_code)
print(r.json())

Seal #

import requests


BASE_URL = "http://127.0.0.1:5000"

payload = {
    "action": "seal"
}

r = requests.post(f"{BASE_URL}/api/bms/studio/security", json=payload, timeout=5)
print(r.status_code)
print(r.json())

15. Complete example: simple console tool #

The script below provides a small console utility that combines the most useful functions: playback monitoring, diagnostics, and cells.

import json
import requests


class BmsTool:
    def __init__(self, base_url="http://127.0.0.1:5000"):
        self.base_url = base_url.rstrip("/")

    def _get(self, path, params=None, timeout=10):
        r = requests.get(f"{self.base_url}{path}", params=params, timeout=timeout)
        r.raise_for_status()
        return r.json()

    def _post(self, path, payload=None, timeout=10):
        r = requests.post(f"{self.base_url}{path}", json=payload or {}, timeout=timeout)
        r.raise_for_status()
        return r.json()

    def monitoring(self):
        return self._get("/api/bms")

    def diag(self):
        return self._get("/api/bms/diag")

    def cells(self):
        return self._get("/api/bms/studio/cells")


if __name__ == "__main__":
    tool = BmsTool()

    print("=== Monitoring ===")
    print(json.dumps(tool.monitoring(), indent=2, ensure_ascii=False))

    print("\\n=== Diagnostic ===")
    print(json.dumps(tool.diag(), indent=2, ensure_ascii=False))

    print("\\n=== Cellules ===")
    print(json.dumps(tool.cells(), indent=2, ensure_ascii=False))

16. Best Practices #

  • Start by /api/bms then /api/bms/diag.
  • Use read-only paths before writing to them.
  • Enable advanced mode only when necessary.
  • Log the values read during the testing and calibration phases.
  • Verify that the number of configured cells matches the actual pack.

Python examples based on the included BMS LC30 API plugin.

Powered by BetterDocs

Leave a comment

Your email address will not be published. Required fields are marked with *

Back to top