Python library and CLI for Kelly motor controllers
Project description
pyKellyMotion
A Python library for interfacing with Kelly motor controllers via serial/UART communication. Provides comprehensive monitoring, configuration read/write, and control capabilities for electric motor systems.
Protocol reverse engineered from Kelly ACAduserEnglish Android app.
Features
- Real-time Monitoring - Motor speed, temperature, current, voltage, switch states
- Configuration Read/Write - Read and modify controller parameters over serial
- Firmware Version Query - Get controller firmware information
- Motor Identification - Enter/exit motor auto-tuning mode
- Checksum Validation - All packets validated for data integrity
- CLI Tool - Command-line interface for quick testing and monitoring
- Clean Pythonic API - Simple interface with type hints and dataclasses
Installation
From PyPI
pip install pykellymotion
From Source
git clone https://github.com/ril3y/pyKellyMotion.git
cd pyKellyMotion
pip install .
Development Install
pip install -e ".[dev]"
Quick Start
Command Line (pykelly)
After installation, the pykelly command is available:
# Real-time monitoring (default)
pykelly COM3
# Monitor with custom interval
pykelly COM3 monitor --interval 0.25
# Get firmware version
pykelly /dev/ttyUSB0 version
# Read controller configuration
pykelly COM3 config
# Read config as raw hex
pykelly COM3 config --raw
# Single monitor read (JSON output)
pykelly COM3 single --json
# Read phase current ADC values
pykelly COM3 phase
# Motor identification mode
pykelly COM3 identify
# Enable debug output
pykelly COM3 --debug monitor
Python Library
from pykellymotion import KellyController
# Connect
controller = KellyController("COM3", debug=False)
controller.connect()
# Monitor
controller.read_monitor()
print(f"RPM: {controller.rpm}")
print(f"Throttle: {controller.throttle}%")
print(f"Battery: {controller.battery_voltage}V")
print(f"Errors: {controller.errors}")
# Read configuration
controller.read_config()
controller.print_config()
max_current = controller.get_config('current_percent')
# Cleanup
controller.disconnect()
Hardware Setup
Wiring
Kelly Controller Computer
TX ────────────────> RX
RX <──────────────── TX
GND ───────────────── GND
Supported Controllers
- KBLS - Kelly BLDC Sensorless/Sensored
- KACI - Kelly AC Induction
- Other Kelly controllers with serial interface
API Reference
KellyController
Connection
from pykellymotion import KellyController
controller = KellyController(comport: str, debug: bool = False)
controller.connect() -> bool
controller.disconnect()
controller.is_connected -> bool
Monitoring
controller.read_monitor() -> bool # Read all monitor packets
controller.start_monitor_loop(callback=None, interval=0.5) # Continuous monitoring
controller.print_monitor() # Print formatted data
Monitor Properties
| Property | Type | Description |
|---|---|---|
rpm |
int | Motor speed in RPM |
motor_mph |
float | Speed in MPH (uses tire_diameter) |
throttle |
int | Throttle position 0-100% |
phase_current |
int | Phase current in amps |
battery_voltage |
int | Battery voltage |
motor_temp |
int | Motor temperature (C) |
controller_temp |
int | Controller temperature (C) |
is_forward |
bool | Forward direction selected |
is_reverse |
bool | Reverse direction selected |
errors |
list | Current error strings |
Configuration
controller.read_config() -> bool # Read config from controller
controller.get_config(param_name: str) -> Any # Get specific parameter
controller.get_all_config() -> dict # Get all parameters
controller.print_config() # Print formatted config
controller.write_config(data: bytes) -> bool # Write raw config (13 bytes) - UNTESTED!
WARNING:
write_config()has NOT been tested on real hardware. Read operations work correctly, but writing could potentially damage your controller. Use at your own risk!
Other Commands
controller.get_version() -> str # Firmware version (hex)
controller.get_phase_current_adc() -> tuple # Raw ADC (a, b, c)
controller.enter_identify_mode() -> bool # Enter motor auto-tune
controller.exit_identify_mode() -> bool # Exit motor auto-tune
controller.is_identify_active() -> bool # Check if tuning active
Low-Level Access
from pykellymotion import Communications, Commands
comm = Communications("COM3", debug=True)
comm.open()
success, data = comm.send_command(Commands.GET_VERSION)
print(f"Version: {data.hex()}")
comm.close()
Configuration Parameters
| Parameter | Description | Range |
|---|---|---|
module_name |
Module identifier | 8 chars ASCII |
serial_number |
Serial number | Hex |
software_version |
Firmware version | Hex |
current_percent |
Max current limit | 20-100% |
battery_current_limit |
Battery current limit | 20-100% |
low_voltage |
Undervoltage cutoff | 0-1000V |
over_voltage |
Overvoltage cutoff | 0-1000V |
max_speed |
Maximum RPM | 0-60000 |
max_forward_speed |
Forward speed limit | 30-100% |
max_reverse_speed |
Reverse speed limit | 20-100% |
tps_type |
Throttle type | 0=None, 1=0-5V, 2=1-4V, 3=0-5K |
tps_dead_low |
Throttle dead zone low | 0-80% |
tps_dead_high |
Throttle dead zone high | 120-200% |
accel_time |
Acceleration ramp (x0.1s) | 0-250 |
brake_time |
Brake ramp (x0.1s) | 0-250 |
regen_brake_percent |
Regen on throttle release | 0-50% |
motor_poles |
Motor pole pairs x2 | 2-32 |
speed_sensor_type |
0=None, 1=Encoder, 2=Hall, 3=Resolver | 0-4 |
high_temp_cutoff |
Motor overtemp cutoff | 60-170C |
Communication Protocol
Serial Settings
| Parameter | Value |
|---|---|
| Baud Rate | 19200 |
| Data Bits | 8 |
| Parity | None |
| Stop Bits | 1 |
Packet Structure
[CMD] [LENGTH] [DATA...] [CHECKSUM]
| Field | Size | Description |
|---|---|---|
| CMD | 1 byte | Command ID |
| LENGTH | 1 byte | Data byte count (0-16) |
| DATA | 0-16 bytes | Command-specific data |
| CHECKSUM | 1 byte | sum(preceding_bytes) & 0xFF |
Special case: When LENGTH = 0, CHECKSUM equals CMD.
Commands
| Command | Hex | Description |
|---|---|---|
| MONITOR_ONE | 0x3A | Real-time data page 1 (throttle, switches, temps) |
| MONITOR_TWO | 0x3B | Real-time data page 2 (RPM, current) |
| MONITOR_THREE | 0x3C | Real-time data page 3 (errors) |
| GET_VERSION | 0x11 | Firmware version |
| READ_CONFIG | 0x4B | Read configuration/calibration |
| WRITE_CONFIG | 0x4C | Write configuration (13 bytes) |
| GET_PHASE_I_AD | 0x35 | Phase current ADC values |
| ENTRY_IDENTIFY | 0x43 | Enter motor identification mode |
| QUIT_IDENTIFY | 0x42 | Exit motor identification mode |
| CHECK_IDENTIFY_STATUS | 0x44 | Check identification status |
Protocol Examples
Read Monitor Data:
TX: 3A 00 3A
RX: 3A 10 [16 bytes data] [checksum]
Read Configuration:
TX: 4B 00 4B
RX: 4B [len] [config data] [checksum]
Get Version:
TX: 11 00 11
RX: 11 [len] [version data] [checksum]
Project Structure
pyKellyMotion/
├── pykellymotion/
│ ├── __init__.py # Public API exports
│ ├── cli.py # CLI entry point (pykelly command)
│ ├── protocol.py # Protocol constants, commands, checksum
│ ├── communications.py # Serial communication layer
│ ├── parser.py # Packet parsing and data extraction
│ └── kelly_controller.py # High-level controller interface
├── tests/
│ ├── test_protocol.py # Protocol unit tests
│ ├── test_parser.py # Parser unit tests
│ └── test_cli.py # CLI argument tests
└── pyproject.toml # Package configuration
Advanced Usage
Custom Monitor Callback
from pykellymotion import KellyController
def my_callback(monitor_data):
print(f"Speed: {monitor_data.motor_speed} RPM")
if monitor_data.motor_temp > 80:
print("WARNING: Motor hot!")
controller = KellyController("COM3")
controller.connect()
controller.start_monitor_loop(callback=my_callback, interval=0.25)
Debug Mode
controller = KellyController("COM3", debug=True)
# Shows TX/RX packets in hex
Or via CLI:
pykelly COM3 --debug monitor
Error Codes
16-bit bitmask decoded by controller.errors:
| Bit | Error |
|---|---|
| 0 | Identification Error |
| 1 | Over Voltage |
| 2 | Low Voltage |
| 4 | Stall |
| 5 | Internal Voltage Fault |
| 6 | Controller Over Temp |
| 7 | Throttle Error (Startup) |
| 9 | Internal Reset |
| 10 | Hall Sensor Error |
| 15 | Motor Over Temp |
Testing
# Install dev dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Run with coverage
pytest --cov=pykellymotion
# Lint and format
ruff check .
ruff format .
Troubleshooting
No Response
- Check TX/RX wiring (may need swap)
- Verify 19200 baud
- Ensure controller powered
- Enable
--debugflag ordebug=True
Permission Denied (Linux)
sudo usermod -a -G dialout $USER
# Log out and back in
Checksum Errors
- Check cable quality
- Ensure clean ground connection
Contributing
- Fork repository
- Create feature branch
- Make changes with tests
- Run
ruff checkandruff format - Submit pull request
License
MIT License - see LICENSE
Acknowledgments
- Protocol reverse engineered from Kelly ACAduserEnglish Android app
- Kelly Controller motor controller products
- Electric vehicle hobbyist community
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pykellymotion-0.1.1.tar.gz.
File metadata
- Download URL: pykellymotion-0.1.1.tar.gz
- Upload date:
- Size: 22.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ffc20f45b5566fe4577f5d8cae4023d6bc1122aa92d03bf521c153c141e6fbe5
|
|
| MD5 |
29d1a8453471a672755c9055bcf218e6
|
|
| BLAKE2b-256 |
ea03f20102a444c5cc77add288f7773b28fae59fe9be35a1e335afbd05277be3
|
Provenance
The following attestation bundles were made for pykellymotion-0.1.1.tar.gz:
Publisher:
publish.yml on ril3y/pyKellyMotion
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pykellymotion-0.1.1.tar.gz -
Subject digest:
ffc20f45b5566fe4577f5d8cae4023d6bc1122aa92d03bf521c153c141e6fbe5 - Sigstore transparency entry: 787712667
- Sigstore integration time:
-
Permalink:
ril3y/pyKellyMotion@968082e46bada45b5f76db33f39bf58f61e36410 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/ril3y
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@968082e46bada45b5f76db33f39bf58f61e36410 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pykellymotion-0.1.1-py3-none-any.whl.
File metadata
- Download URL: pykellymotion-0.1.1-py3-none-any.whl
- Upload date:
- Size: 18.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3c4c13129a775be67192241a4b5415170e626eb8d0b0237ce6065068862f5230
|
|
| MD5 |
76aa49549c53fabe6a01ef9a2fabb807
|
|
| BLAKE2b-256 |
2b9cba12e7dea1c6df0795989cd4aec7d98900eaa3e73c67f39e2288ae6c9b85
|
Provenance
The following attestation bundles were made for pykellymotion-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on ril3y/pyKellyMotion
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pykellymotion-0.1.1-py3-none-any.whl -
Subject digest:
3c4c13129a775be67192241a4b5415170e626eb8d0b0237ce6065068862f5230 - Sigstore transparency entry: 787712670
- Sigstore integration time:
-
Permalink:
ril3y/pyKellyMotion@968082e46bada45b5f76db33f39bf58f61e36410 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/ril3y
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@968082e46bada45b5f76db33f39bf58f61e36410 -
Trigger Event:
release
-
Statement type: