Python driver for the ISDT MP305 (MP305A/MP305B) smart bench power supply over USB-HID
Project description
pymp305
A pure-Python driver for the ISDT MP305 smart bench power supplies (MP305A and
MP305B) over USB-HID (sync) and Bluetooth (async) — the same protocol the
official WebLink web app and PolyLink phone app use.
Reverse-engineered from WebLink's public source-maps; the full protocol is documented in
../PROTOCOL.md. (The recovered upstream JS is ISDT's copyright and is
kept locally under reversing/, which is git-ignored and not published.)
Status: not yet validated against real hardware. Framing, decoding, units, and firmware-decrypt are covered by golden-vector tests (
pytest, all passing), but nothing here has talked to a device —open()emits a one-timeUserWarningto that effect, and OTA flashing is gated behind an explicitallow_untested_ota=True. See Bring-up below.
Install
pip install pymp305 # USB-HID (pulls in hidapi)
pip install pymp305[ble] # + Bluetooth transport (bleak)
From source (development): pip install -e . from this directory.
A Dracula-themed PyQt6 desktop GUI (live charts + simulator) lives in ../gui.
On Linux you'll need permission to access the hidraw node. Either run as root for a quick test, or add a udev rule (recommended):
# /etc/udev/rules.d/99-pymp305.rules
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="28e9", MODE="0660", TAG+="uaccess"
then sudo udevadm control --reload && sudo udevadm trigger and replug.
Quick start
from pymp305 import MP305
with MP305.open() as psu:
print(psu.hardware_info()) # name, hw/boot/app versions
psu.set_output(voltage=5.0, current=1.0, on=True) # takes remote control + enables output
st = psu.read_state()
print(f"{st.voltage:.2f} V {st.current:.3f} A {st.power:.2f} W {st.temperature} C")
psu.output_off()
psu.release_remote() # hand control back to the front panel
See examples/basic.py for a live-streaming example.
API surface
| Method | Does |
|---|---|
MP305.list_devices() |
enumerate VID 0x28E9 HID interfaces |
MP305.open(path=None) |
open (auto-picks usage_page 0x01 / usage 0x04) |
hardware_info() |
0xE0→0xE1 device id, firmware versions |
read_state(realtime=True) |
0xBD/0xC2→0xC3 live V/I/W/Wh/temp/output (State) |
read_system_settings() |
0xC4→0xC5 (SystemSettings) |
set_output(voltage, current, on, model=0) |
take control + apply, returns fresh State |
output_on() / output_off() |
toggle output |
release_remote() |
remoteCon=0 — return control to the panel |
control(ControlCommand) |
low-level 0xC8 |
set_system_settings(SystemSetCommand) |
0xC6 |
set_language(i) |
0xA2 |
read_charge_state() / read_charge_settings() |
0xEC→0xED / 0xEA→0xEB (ChargeState / ChargeInfo) |
charge(ChargeCommand) |
0xEE start/stop a battery charge |
read_pdo(id) / pdo_connect(PDOConnect) / write_pdo(...) |
0xD0→0xD1 read / 0xE8→0xE9 select / 0xD2→0xD3 define a PD profile |
read_program_list() / read_program_steps(id, n) |
0xD4→0xD5 / 0xD8→0xD9 |
read_program_state() / program_connect(ProgramConnect) |
0xDE→0xDF / 0xE2→0xE3 run a sequence |
write_program(id, steps) / program_change(...) |
0xDA→0xDB write steps / 0xD6→0xD7 create/rename/delete slot |
read_emarker() |
USB-C cable e-marker info (speed/format labelled) |
flash(Firmware, allow_untested_ota=True) |
experimental OTA over HID (see warnings) |
reboot() / enter_bootloader() |
danger zone |
send(cmd, payload) / request(cmd, expect, payload) / read_frame() |
raw access for any other command |
Units are converted for you: voltage/set_voltage in V, current/set_current
in A, power in W, energy in Wh, temperature in °C, working_time in s.
Transports
MP305— synchronous, USB-HID (this table).MP305BLE— the same API butasyncover Bluetooth (bleak); methods are coroutines and connection isawait MP305BLE.open(). Addsflash_ble(IntelHexFirmware, allow_untested_ota=True)(experimental BLE OTA overfee0/fee1). Seeexamples/ble.py.
Firmware (pymp305.ota)
Firmware.parse(bytes)— decrypt + verify an official.bin(key is in the header).IntelHexFirmware.parse(...)— the BLE FEE1 firmware format.tools/fetch_firmware.pydownloads + decrypts official images into git-ignoredreversing/.- OTA writing is experimental and untested on hardware — see the repo banner.
Experimental / undisclosed commands
Reverse-engineered from the firmware (handled by the device but never sent by the official
app). Untested on hardware — see notes in reversing/FINDINGS-commands.md.
get_language()— read the UI language index (cmd0xA0→0xA1), the read counterpart ofset_language(). Read-only; the safe first probe.soft_reset(confirm=True)— magic-gated (0xFE AA 55) soft re-init of the regulator/ USB-PD state to defaults + control-task restart. Static analysis shows no flash/NVM access (can't brick), but it resets the live output, so it's gated behindconfirm=True.
Bring-up checklist (first run with hardware)
python -c "from pymp305 import MP305; print(MP305.list_devices())"— confirm the device shows up under VID0x28E9and note itsusage_page/usage.psu.hardware_info()— if the de-stuffed name/version look right, framing is correct.- If reads time out: the device may not use a fixed 64-byte report. Try
MP305(dev, report_size=N)with other sizes, or pass an explicit interfacepath. read_state()polls with the realtime command (0xBD) like the app; if that yields nothing, tryread_state(realtime=False)(0xC2).
Tests
pytest # runs all suites (framing, charge/PD/program, OTA, experimental)
All run without hardware — they validate framing/checksum/stuffing, unit decoding, the firmware decryptor, and the command builders against golden vectors.
BLE transport
Implemented via MP305BLE (async, bleak) — see Transports above and
examples/ble.py. Over BLE the same command set is used but frames
drop the length/0xAA/checksum wrapper ([0x12, cmd, …payload]): commands go to GATT
characteristic af01, responses arrive as notifications (parsed at index 2, reusing
responses.py), and an af02 binding handshake starts the session; fee0/fee1 carry BLE OTA.
MP305 has Bluetooth, but its BLE-module firmware isn't published in the OTA feed and this transport is unverified on hardware — USB-HID is the primary path.
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 pymp305-0.5.1.tar.gz.
File metadata
- Download URL: pymp305-0.5.1.tar.gz
- Upload date:
- Size: 35.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fda964d3cc588760dd810e3a0ec7cb55243aa73d95e3fe4ab0f54fa4b6b1400f
|
|
| MD5 |
ed3cf829cbb490c02e4e36347f407c0a
|
|
| BLAKE2b-256 |
25333371f77f40f1713f7b6e2e2bb0e1fff251fdda71be72f5d2e76eac55cde8
|
Provenance
The following attestation bundles were made for pymp305-0.5.1.tar.gz:
Publisher:
publish.yml on nemanjan00/pymp305
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pymp305-0.5.1.tar.gz -
Subject digest:
fda964d3cc588760dd810e3a0ec7cb55243aa73d95e3fe4ab0f54fa4b6b1400f - Sigstore transparency entry: 1858009024
- Sigstore integration time:
-
Permalink:
nemanjan00/pymp305@58ea290de796417acd369e94814c043adf7eebeb -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/nemanjan00
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@58ea290de796417acd369e94814c043adf7eebeb -
Trigger Event:
push
-
Statement type:
File details
Details for the file pymp305-0.5.1-py3-none-any.whl.
File metadata
- Download URL: pymp305-0.5.1-py3-none-any.whl
- Upload date:
- Size: 29.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b82fc57ff5104340c81f947de54d83327ee329c869000f12ebec282ec948e6fe
|
|
| MD5 |
fdb8b97e5b7a3f8af6fd5012b0787a65
|
|
| BLAKE2b-256 |
7b954c524ab988cecea8d00efef3344184a1136ec3c264b7709feabf34abb882
|
Provenance
The following attestation bundles were made for pymp305-0.5.1-py3-none-any.whl:
Publisher:
publish.yml on nemanjan00/pymp305
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pymp305-0.5.1-py3-none-any.whl -
Subject digest:
b82fc57ff5104340c81f947de54d83327ee329c869000f12ebec282ec948e6fe - Sigstore transparency entry: 1858009065
- Sigstore integration time:
-
Permalink:
nemanjan00/pymp305@58ea290de796417acd369e94814c043adf7eebeb -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/nemanjan00
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@58ea290de796417acd369e94814c043adf7eebeb -
Trigger Event:
push
-
Statement type: