- BMS LC30 – Complete Python Examples
- 1. Prerequisites
- 2. Minimal API client
- 3. Read the key data from the BMS
- 4. I²C Diagnostics
- 5. View and edit advanced settings
- 6. Voltage and Current Calibration
- 7. Read a register directly
- 8. Write a log
- 9. Use register profiles
- 10. Device and cell information
- 11. Reading statuses and flags
- 12. Mac Commands
- 13. Data Flash
- 14. Safety Management
- 15. Complete example: simple console tool
- 16. Best Practices
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())
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())
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/bmsthen/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.
