A pure Python library for controlling the FNIRSI DPS-150 programmable power supply via serial communication. This library provides a clean async/await API for all device operations without any UI dependencies.
- Async/await support - Non-blocking operations for efficient device control
- Complete protocol implementation - Based on the JavaScript reference implementation
- Full device control - All device features accessible via Python
- State monitoring - Real-time state updates with callback support
- Type hints - Full type annotations for better IDE support
- Comprehensive examples - Jupyter notebook with practical examples
- ✅ Voltage and current control (0-30V, 0-5A typical)
- ✅ Protection settings (OVP, OCP, OPP, OTP, LVP)
- ✅ Preset groups (6 configurable presets)
- ✅ Energy metering (capacity in Ah, energy in Wh)
- ✅ Device information (model, firmware, hardware versions)
- ✅ Display and audio settings (brightness, volume)
- ✅ Real-time monitoring (voltage, current, power, temperature)
git clone <repository-url>
cd DPS-150
pip install -r requirements.txtOr install as a package:
pip install .- Python 3.9 or higher
- pyserial-asyncio >= 0.6
- pyserial >= 3.5
import asyncio
from dps150 import DPS150
async def main():
# Connect to device
async with DPS150(port="/dev/ttyUSB0") as device: # or "COM3" on Windows
# Get device information
info = await device.get_info()
print(f"Model: {info.model_name}")
print(f"Firmware: {info.firmware_version}")
# Set voltage and current
await device.set_voltage(12.0)
await device.set_current(1.0)
# Enable output
await device.enable_output()
# Read current state
state = await device.get_all()
print(f"Output: {state.output_voltage:.3f}V @ {state.output_current:.3f}A")
# Disable output
await device.disable_output()
if __name__ == "__main__":
asyncio.run(main())from dps150 import list_serial_ports
# List all available serial ports
ports = list_serial_ports()
for port in ports:
print(f"Device: {port['device']}")
print(f" Description: {port['description']}")
print(f" VID: {port['vid']}, PID: {port['pid']}")import asyncio
from dps150 import DPS150
async def main():
async with DPS150(port="/dev/ttyUSB0") as device:
await device.set_current(1.0) # Set current limit
await device.enable_output()
# Sweep voltage from 0 to 12V
for voltage in range(0, 13):
await device.set_voltage(float(voltage))
await asyncio.sleep(0.5) # Wait at each voltage
state = await device.get_all()
print(f"{voltage}V: {state.output_voltage:.3f}V, "
f"{state.output_current:.3f}A, {state.output_power:.3f}W")
await device.disable_output()
asyncio.run(main())import asyncio
from dps150 import DPS150
async def main():
async with DPS150(port="/dev/ttyUSB0") as device:
# Set protection limits
await device.set_ovp(15.0) # Over-voltage protection at 15V
await device.set_ocp(2.0) # Over-current protection at 2A
await device.set_opp(20.0) # Over-power protection at 20W
# Verify settings
state = await device.get_all()
print(f"OVP: {state.over_voltage_protection:.2f}V")
print(f"OCP: {state.over_current_protection:.2f}A")
print(f"OPP: {state.over_power_protection:.2f}W")
asyncio.run(main())import asyncio
from dps150 import DPS150
async def main():
async with DPS150(port="/dev/ttyUSB0") as device:
# Register callback for state updates
def on_update(state):
print(f"V: {state.output_voltage:.3f}V, "
f"I: {state.output_current:.3f}A, "
f"P: {state.output_power:.3f}W")
if state.protection_state.value:
print(f"⚠ Protection: {state.protection_state.value}")
device.on_state_update(on_update)
await device.set_voltage(10.0)
await device.set_current(1.0)
await device.enable_output()
# Monitor for 10 seconds
await asyncio.sleep(10)
await device.disable_output()
asyncio.run(main())import asyncio
from dps150 import DPS150
async def main():
async with DPS150(port="/dev/ttyUSB0") as device:
# Configure preset groups
await device.set_group(1, 5.0, 0.5) # Group 1: 5V @ 0.5A
await device.set_group(2, 12.0, 1.0) # Group 2: 12V @ 1.0A
# Load and use group 1
await device.load_group(1)
await device.enable_output()
await asyncio.sleep(2)
# Switch to group 2
await device.load_group(2)
await asyncio.sleep(2)
await device.disable_output()
asyncio.run(main())Create a DPS150 device instance.
Parameters:
port(str, optional): Serial port path (e.g.,/dev/ttyUSB0on Linux/Mac,COM3on Windows). IfNone, attempts auto-detection.
Example:
device = DPS150(port="/dev/ttyUSB0")Connect to the device and initialize communication.
Raises:
DPS150ConnectionError: If connection fails
Disconnect from the device and clean up resources.
The DPS150 class supports async context manager for automatic connection management:
async with DPS150(port="/dev/ttyUSB0") as device:
# Device is automatically connected
await device.set_voltage(12.0)
# Device is automatically disconnectedGet complete device state including all measurements and settings.
Returns:
DeviceState: Complete device state object
Get current output voltage.
Returns:
float: Output voltage in volts
Get current output current.
Returns:
float: Output current in amperes
Get current output power.
Returns:
float: Output power in watts
Get device temperature.
Returns:
float: Temperature in degrees Celsius
Get device information (model name, firmware version, hardware version).
Returns:
DeviceInfo: Device information object
Set target output voltage.
Parameters:
value(float): Voltage in volts (typically 0-30V)
Set target output current (current limit).
Parameters:
value(float): Current in amperes (typically 0-5A)
Enable the output (turn on power supply).
Disable the output (turn off power supply).
Set over-voltage protection threshold.
Parameters:
value(float): Voltage threshold in volts
Set over-current protection threshold.
Parameters:
value(float): Current threshold in amperes
Set over-power protection threshold.
Parameters:
value(float): Power threshold in watts
Set over-temperature protection threshold.
Parameters:
value(float): Temperature threshold in degrees Celsius
Set low-voltage protection threshold.
Parameters:
value(float): Voltage threshold in volts
Set preset group values.
Parameters:
group(int): Group number (1-6)voltage(float): Voltage in voltscurrent(float): Current in amperes
Raises:
ValueError: If group number is not 1-6
Load preset group values as current settings.
Parameters:
group(int): Group number (1-6)
Raises:
ValueError: If group number is not 1-6
Start energy metering (accumulates capacity and energy).
Stop energy metering.
Set display brightness.
Parameters:
value(int): Brightness level (0-10)
Raises:
ValueError: If value is not 0-10
Set beep volume.
Parameters:
value(int): Volume level (0-10)
Raises:
ValueError: If value is not 0-10
Register a callback function for state updates. The callback will be called whenever the device state changes.
Parameters:
callback(Callable): Function that takes aDeviceStateparameter
Example:
def my_callback(state: DeviceState):
print(f"Voltage: {state.output_voltage}V")
print(f"Current: {state.output_current}A")
if state.protection_state != ProtectionState.NORMAL:
print(f"⚠ Protection: {state.protection_state.value}")
device.on_state_update(my_callback)Complete device state dataclass containing all measurements and settings.
Key Fields:
output_voltage(float): Current output voltageoutput_current(float): Current output currentoutput_power(float): Current output powertemperature(float): Device temperatureset_voltage(float): Set voltage valueset_current(float): Set current valueover_voltage_protection(float): OVP thresholdover_current_protection(float): OCP thresholdover_power_protection(float): OPP thresholdoutput_capacity(float): Energy capacity in Ahoutput_energy(float): Energy in Whoutput_closed(bool): Output enabled/disabledprotection_state(ProtectionState): Current protection statemode(Mode): Output mode (CC or CV)
Device information dataclass.
Fields:
model_name(str): Model namehardware_version(str): Hardware versionfirmware_version(str): Firmware version
Protection state enumeration.
Values:
NORMAL: Normal operationOVP: Over Voltage Protection triggeredOCP: Over Current Protection triggeredOPP: Over Power Protection triggeredOTP: Over Temperature Protection triggeredLVP: Low Voltage Protection triggeredREP: Reverse Connection Protection triggered
Output mode enumeration.
Values:
CC: Constant Current modeCV: Constant Voltage mode
Base exception for all DPS-150 errors.
Raised when there are connection issues with the device.
Raised when there are protocol errors (bad checksum, malformed packet, etc.).
Raised when a communication timeout occurs.
Raised when a protection state is triggered.
Attributes:
protection_state(str): The protection state that was triggered
The library implements the DPS-150 serial communication protocol:
- Baud Rate: 115200
- Data Bits: 8
- Stop Bits: 1
- Parity: None
- Flow Control: Hardware (RTS/CTS)
Outgoing packets:
[0xF1, command, type, length, data..., checksum]
Incoming packets:
[0xF0, command, type, length, data..., checksum]
Checksum calculation:
checksum = (type + length + sum(data_bytes)) % 256
Data encoding:
- Float values: Little-endian 32-bit IEEE 754 format
- String values: UTF-8 encoded, null-terminated
- Byte values: Single byte (0-255)
0xA1: GET - Request value from device0xB1: SET - Set value on device0xC1: Connection/initialization command
DPS-150/
├── dps150/ # Main package
│ ├── __init__.py # Package exports
│ ├── device.py # Main DPS150 class
│ ├── protocol.py # Packet encoding/decoding
│ ├── transport.py # Serial communication layer
│ ├── models.py # Data models (DeviceState, DeviceInfo)
│ ├── constants.py # Protocol constants
│ ├── exceptions.py # Custom exceptions
│ └── utils.py # Utility functions
├── tests/ # Test suite
├── example.ipynb # Jupyter notebook examples
├── setup.py # Package setup
├── requirements.txt # Dependencies
└── README.md # This file
Run the test suite:
pytestFor coverage:
pytest --cov=dps150 --cov-report=htmlContributions are welcome! Please feel free to submit a Pull Request.
MIT License - see LICENSE file for details.
This library is based on the JavaScript implementation by cho45. Special thanks for reverse-engineering the protocol and providing the reference implementation.
- JavaScript/WebSerial Implementation - Original JavaScript implementation
- FNIRSI DPS-150 Product Page
For issues, questions, or feature requests, please open an issue on GitHub.