Python library for controlling Junsi iCharger battery chargers via USB HID Modbus protocol.
Tested with: iCharger 308 DUO (should work with 4010 DUO, 406 DUO, X8, X12, and other Modbus-compatible models)
- USB HID Modbus - Reliable communication via USB (no FTDI adapter needed)
- Battery Fingerprinting - Unique identification via IR Ratio + OCV Pattern
- Auto IR Measurement - Measure internal resistance without manual interaction
- Preset Management - Read and use charger presets programmatically
- Real-time Monitoring - Live voltage, current, temperature, and cell data
- Full Control - Start/stop charge, discharge, storage, balance operations
Connect iCharger via Mini USB cable. Ensure USB mode is enabled:
System Menu → Communication → Select: USB
# Requires Python 3.7+ with hidapi
pip install hidapi
# macOS may need:
brew install hidapi
pip install hidapifrom modbus_usb import ModbusUSB
modbus = ModbusUSB()
modbus.connect()
# Read device ID
regs = modbus.read_input_registers(0x0000, 1)
print(f"Device ID: {regs[0]}") # 66 = iCharger 308 DUO
modbus.disconnect()from modbus_usb import ModbusUSB
modbus = ModbusUSB()
modbus.connect()
# Read channel 1 data
regs = modbus.read_input_registers(0x0100, 20)
current = regs[4]
if current >= 0x8000:
current = current - 0x10000
current_ma = current * 10
voltage = regs[6]
cells = regs[11:19]
print(f"Voltage: {voltage}mV")
print(f"Current: {current_ma}mA")
print(f"Cells: {[c for c in cells if 2500 < c < 4500]}")
modbus.disconnect()python auto_ir_v3.pyOutput:
AUTO IR MEASUREMENT v3
Connected: Junsi iCharger 308Duo USB HID
1. Reading OCV...
Cells: 4S
OCV: [3818, 3818, 3818, 3814] mV
2. Starting discharge (preset 0 = 5A)...
3. Waiting for current to reach 4000mA...
4. Stopping discharge...
IR CALCULATION
Max current: 4440 mA
Cell OCV Load ΔV IR
1 3815mV 3685mV 130mV 29.3mΩ
2 3815mV 3688mV 127mV 28.6mΩ
3 3815mV 3684mV 131mV 29.5mΩ
4 3811mV 3663mV 148mV 33.3mΩ
🔑 IR RATIO (fingerprint):
Cell 1: 0.970 (-3.0%)
Cell 2: 0.948 (-5.2%) ← UNIQUE
Cell 4: 1.104 (+10.4%) ← UNIQUE
The library provides unique battery identification using two methods:
The ratio of each cell's internal resistance to the pack average. This metric is independent of wire/connector resistance due to the Kelvin sensing effect of balance leads.
IR_ratio[i] = IR[i] / mean(IR_all)
Open Circuit Voltage differences between cells, which remain stable over the battery's life.
See BATTERY_FINGERPRINT.md for detailed theory and implementation.
python-controller/
├── icharger_const.py # All constants (addresses, opcodes, status codes)
├── icharger_core.py # Shared utilities (read_channel, detect_cells, etc.)
├── modbus_usb.py # USB HID Modbus transport (recommended)
├── modbus_rtu.py # Serial Modbus RTU (for FTDI adapter)
│
├── auto_ir.py # Automatic IR measurement via discharge pulse
├── capture_ir.py # Capture IR during manual Get IR
├── read_presets.py # Read charger presets
├── debug_status.py # Debug discharge status
├── battery_fingerprint.py # Battery fingerprinting system
├── icharger.py # High-level API (Serial-based)
├── test_icharger.py # Interactive test CLI
│
├── docs/
│ ├── iCharger_308DUO_Manual_EN.pdf
│ └── iCharger_MODBUS_Protocol_V2.1.pdf
│
├── archive/ # Old/superseded versions
│
├── BATTERY_FINGERPRINT.md # Fingerprinting theory
├── SERIAL_MODBUS_RESEARCH.md # Protocol research
├── TEST_PLAN.md # Test documentation
└── README.md # This file
| Address | Description |
|---|---|
| 0x0000 | Device Info (ID, Serial, Versions) |
| 0x0100 | Channel 1 Data |
| 0x0200 | Channel 2 Data |
| Address | Description |
|---|---|
| 0x8000 | Control Registers |
| 0x8400 | System Settings |
| 0x8800 | Preset Index (64 slots) |
| 0x8C00 | Preset Data |
| Offset | Name | Type | Units |
|---|---|---|---|
| 0-1 | Timestamp | U32 | ms |
| 2-3 | Power | U32 | mW |
| 4 | Current | S16 | 10mA |
| 5 | Input Voltage | U16 | 10mV |
| 6 | Output Voltage | U16 | mV |
| 9 | Internal Temp | S16 | 0.1°C |
| 11-18 | Cell Voltages | U16[8] | mV |
| 35-42 | Cell IR | U16[8] | 0.1mΩ |
| 55 | Run Status | U16 | See codes |
| 56 | Run Error | U16 | Error code |
| Code | Description |
|---|---|
| 0 | STOP |
| 1 | WAIT_START |
| 2 | CHARGE_CC |
| 3 | CHARGE_CV |
| 5 | DISCHARGE_CC |
| 6 | DISCHARGE_CV |
| 13 | PROTECT |
| 43 | MEASURE_IR |
# 1. Select memory preset
modbus.write_multiple_registers(0x8001, [memory_slot, channel, 0x55AA])
# 2. Set operation
modbus.write_multiple_registers(0x8000, [operation, memory_slot, channel])
# 3. Execute
modbus.write_multiple_registers(0x8003, [0x55AA, 1]) # 1 = RUN
# 4. Stop
modbus.write_multiple_registers(0x8002, [channel, 0x55AA, 0]) # 0 = STOP| Feature | USB HID | Serial (FTDI) |
|---|---|---|
| Works during charging | Yes | No (disconnects) |
| Hardware | Mini USB cable | FTDI adapter |
| Reliability | Excellent | Poor |
| Speed | ~350 reads/sec | ~100 reads/sec |
Recommendation: Always use USB HID.
- Check USB connection
- Verify USB mode is enabled in charger settings
- Try different USB port
- Check
hidapiis installed:python -c "import hid; print(hid.enumerate())"
# Add udev rule
echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5751", MODE="0666"' | sudo tee /etc/udev/rules.d/99-icharger.rules
sudo udevadm control --reload-rulesThis error (0x1D00) may appear but doesn't prevent operation. The charger still functions correctly.
- iCharger MODBUS Protocol V2.1
- iCharger 308 DUO Manual
- johncclayton/electric - Reference implementation
MIT License - Free for personal and commercial use.
Created for FPV drone battery management and fingerprinting.