last updated: 2025-05-22
The DP100 is a tiny power supply and fits very well on your desk without taking space. It has 2 USB connectors. The USB-C connector is the power input up to 32 V, 5 A with an USB-PD power supply (or another DC alimentation up to 32 V). It has a tiny OLED display, three buttons and an adjustment wheel. The second USB-A connector can also be used to provide power (5 V/1 A) but is primarily needed to connect the device to a PC and to use the PC Windows software.
I love Linux, but mostly software is only written for Windows and Mac. Unfortunately the USB data transfer protocols of products are often proprietary and so it's hard to write software for Linux. To do so we need to understand the proprietary protocol used on USB. Her my new Cynthion helped to sniff the traffic. For more information : https://www.weigu.lu/other_projects/usb/cynthion/index.html.
I wrote a Python software for Linux to manipulate the DP100. It can surely also be used on Mac and Windows, but I will not cover that here. My software covers the basics needed but can surely be enhanced e.g. to load batteries. So feel free to use and enhance it.
The DP100 uses HID to avoid a driver. We have a vendor designed usage. No HID class is used. The exchange of data is done with a field of 64 byte (8 bit). The meanings are normally only known to the vendor.
I will use the python hidapi library to communicate with the device. I was not able to run the software without being root (sudo
). So the writing of udev rules is not not absolutely necessary. But let's do it anyway. The vendor ID is 0x2E3C
and the product ID is 0xAF01
.
cd /etc/udev/rules.d
sudo nano 55-ATK-MDP100.rules
Copy the following lines in the editor and save (Ctrl+o, Ctrl+x) and reload the rules.
# DP100
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2e3c", ATTRS{idProduct}=="af01", MODE="0660", GROUP="plugdev"
sudo udevadm control --reload
sudo udevadm trigger
We can test test the udev rules with:
udevadm test /sys/class/hidraw/hidraw0
ls -l /dev/hidraw0
The IN and OUT transactions (we have only 2 endpoints) use 64 byte. So we will always send and receive 64 byte (0-63). The host PC sends always 0xFB
as first byte (OUT) and get's an 0xFA
(IN) from the device. The second byte is a command. The third byte is always 0x00
.
I will use 3 commands: 0x10
(device info), 0x30
basic info and 0x35
(switch on/off, manipulate profiles).
Byte | 0 | 1 | 2 | 3 | 4–3+n | 4+n | 5+n |
---|---|---|---|---|---|---|---|
Meaning | 0xfb/0xfa | command | 0x00 | len data n | ... | crcL | crcH |
The fourth byte is the length of the following data. Then comes the data and the two last bytes are a checksum called CRC-16-Modbus. The data values with two bytes (16 bit) and the checksum are send with the lowest byte coming first also called as little endian!
From host (PC):
Byte | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
Basic Set | 0xfb | 0x10 | 0x00 | 0x00 | crc 0x30 | crc 0xc5 |
From device (DP100):
Byte | 0 | 1 | 2 | 3 | 4–19 | 20–21 | 22–3 | 24–35 | 36–39 | 40–43 | 44–45 |
---|---|---|---|---|---|---|---|---|---|---|---|
Device Info | 0xfa | 0x10 | 0x00 | 0x28 | name | hver | sver | ... | serial nr | ... | crc |
The first 9 data byte give us the name "ATP-DP100" (ASCII code, bytes). We also see the hardware and software version of the device (2 bytes) and the serial number (bytes).
The hardware and software versions are coded as 16 bit. 0x0E00
gives in the right order 0x000E
= 14. The version is 1.4.
The 0x30
command returns basic information. Most interesting are the output voltage and current (vout and iout).
From host (PC):
Byte | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
Basic Set | 0xfb | 0x30 | 0x00 | 0x00 | crc 0x31 | crc 0x0f |
From device (DP100):
Byte | 0 | 1 | 2 | 3 | 4–5 | 6–7 | 8–9 | 10–19 | 20–21 |
---|---|---|---|---|---|---|---|---|---|
Basic Info | 0xfa | 0x30 | 0x00 | 0x10 | vin | vout | iout | ... | crc |
As an example:
If we get the following data bytes for vout, iout: 0x8D
, 0x13
, 0x17
, 0x00
, we obtain 0x138D
= 5005 and 0x0017
= 23.
The voltage is 5005 mV and the current is 23 mA.
First we must understand, that the DP100 only works with 10 profiles containing the set voltage and the set current. There is no other way to manipulate the voltage and the current. If we switch the device on or off the voltage and current of the activated profile is used. And even more complications. To change a profile, activate it or switch on and off we need to first get the data from the profile, change one byte or more and send it back!
So first to be able to switch on and off we need to get the activated profile:
If we send the command 0x35
with the data byte 0x80
we get the data for the active profile.
From host (PC):
Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
Basic Set | 0xfb | 0x35 | 0x00 | 0x01 | 0x80 | crc 0xce | crc 0x28 |
From device (DP100):
Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6–7 | 8–9 | 10–11 | 12–13 | 14–15 |
---|---|---|---|---|---|---|---|---|---|---|---|
Basic Set | 0xfa | 0x35 | 0x00 | 0xa | index | state | vo_set | io_set | OVP | OCP | crc |
Index gives us the information which profile is active and state informs us if the output is on or off!
If we replace 0x80
with 0x00
–0x09
we can read voset and ioset for all profiles.
To change a profile, we need to read the profile and save the data. Then we change the first data byte (byte 4) and the values for voltage and current.
We recalculate the CRC and send the data back.
The upper nibble of byte 4 must be 0x4 and the lower nibble is the profile number (e.g. to change third profile (number 2) we send 0x42
).
If we want to change voltage or/and current we need to activate the profile with the wanted values or even first set the values to a profile and than activate it.
To do so we need to read the profile and save the data. Then we change the first data byte (byte 4), recalculate the CRC and send the data back.
The upper nibble of byte 4 must be 0xA and the lower nibble is the profile number (e.g. to activate the forth profile (number 3) we send 0x83
).
To switch on/off we have to look which profile is activated. Then we read this profile, save the data and change the first and the second data byte (Byte 4 and 5), recalculate the CRC and send the data back.
The upper nibble of byte 4 must be 0x2 and the lower nibble is the profile number (e.g. to switch on/off the active profile number 5 we send 0x25
).
Byte 5 is 0x01
to switch on and 0x00
to switch off.
It is written in Python and uses the Python Tkinter GUI. The software runs in a virtual environment under Linux. More about Python virtual environments: https://www.weigu.lu/other_projects/python_coding/using_venv/index.html.
The software uses or creates the following files:
dp100.py
:
Main file creating three threads and 3 queues to communicate between the threads. Mor about this: https://www.weigu.lu/other_projects/python_coding/using_threading/index.html. The mainloop updates the data by calling the different functions contained in dp100_functions.py
and checks the data coming from the gui queue. It has a delay from 1 second to update everything. I is possible to go faster but I think 1s is ok.
dp100_functions.py
:
Contains different functions in a class called DP100Functions
. The available functions are: get_device_info()
, get_basic_info()
,get_active_profile()
,get_profiles()
, change _profile(nr, vo_set, io_set)
, activate profile(nr)
, on_off()
(toggles), and off()
. We also have a helper function to calculate the CRC: modbus_crc(buf)
and a function to create the png time diagram: create_png_from_csv(csv_filename, png_filename)
. This function runs in an own thread to not block the GUI. The function for this is png_creation_thread()
.
dp100_gui.py
:
Containing the GUI class. We use Python Tkinter, but also ttk also contained in Python.
dp100.png
:
Containing an image with text for the GUI header (created with inkscape).
dp100.sh
:
A simple bash script to source the virtual environment (more below)
dp100_vi_data.csv
:
When the software is running this data file is created once per second and contains datetime, voltage and current.
dp100_vi_data.png
:
A png file containing a time diagram created with the Python library matplotlib
from the CSV file.
Unfortunately it must be run as root (sudo). Even with udev rules and all rights set it was not possible to open the device through hidapi. Perhaps this is because of the virtual environment?
Default folder is our home folder. Get the program from github (install git if not already installed) and use the bash script to run it:
cd ~
sudo apt install git
git clone https://github.com/weigu1/dp100_manipulator.git
cd dp100_manipulator
./dp100.sh
The script only sources the virtual environment and runs dp100.py as root. If you want to use another folder you have to adjust the two lines in the script:
#!/bin/bash
source $HOME/dp100_manipulator/Python/.venv/bin/activate
sudo python $HOME/dp100_manipulator/Python/dp100.py
You can find all the used libraries in the requirement.txt
file in the Python folder.
But the main libraries to install are are mathplotlib
and hidapi
.
As an example of one of the functions we will look at the function on_off()
. First we have to check which profile is activated with get_active_profile_info()
.
This function returns a dictionary, In the dictionary we find the number of the active profile under "index" and "state" tells us if the dp100 is switched on or off.
Now we need to create an byte array with 64 byte. The line data_get_profile = bytearray(64)
does this and even fills all the byte with 0x00
.
The first byte must be 0xfb
and then comes the command (0x35
). The third byte is always 0x00
and the forth byte is the length of the data send. Here it is 0x01
because we send only one byte with the number of the active profile to get the data of this profile. Now we calculate the CRC and write the 64 byte to the DP100.
After a check of the 4 first byte of the response of the device we copy the response to our byte array and override everything but the first byte 0xfb
.
Now we change the first and the second data byte (Byte 4 and 5). The upper nibble of byte 4 must be 0x2 and the lower nibble is the profile number (e.g. to switch on/off the active profile number 5 we send 0x25
). If the state is off
we switch off with Byte 5 (0x01
) and if the state is on
we switch off with Byte 5 0x00
.
Then we recalculate the CRC and send the data back to the device. If the response is OK (1) we fill the queue to the GUI to tell that the state changed.
def on_off(self):
""" Switch on/off current profile"""
answer = self.get_active_profile_info()
nr = answer["index"]
state = answer["state"]
data_get_profile = bytearray(64)
data_get_profile[0:4] = 0xfb, 0x35, 0x00, 0x01
try:
device = hid.device() # Open the device
device.open(self.vendor_id, self.product_id)
data_get_profile[4] = nr
crc = self.modbus_crc(data_get_profile[0:5])
data_get_profile[5] = crc[0]
data_get_profile[6] = crc[1]
device.write(data_get_profile) # Send the data
response = device.read(64, timeout_ms=5000) # Read the resp. 5s timeout
#print(' '.join(format(x, '02x') for x in response[0:17]))
if len(response) == 64 and response[0] == 0xFA and response[1] == 0x35 \
and response[2] == 0x00 and response[3] == 0x0a:
data_get_profile[1:15] = response[1:15]
# changing bytes
data_get_profile[4] = 0x20 + nr # 0x20 to switch
if state: # if on
data_get_profile[5] = 0x00 # switch off
#print("we switch off")
else:
data_get_profile[5] = 0x01 # switch on
#print("switch on")
crc = self.modbus_crc(data_get_profile[0:14])
data_get_profile[14] = crc[0]
data_get_profile[15] = crc[1]
#print(' '.join(format(x, '02x') for x in data_get_profile[0:17]))
device.write(data_get_profile) # Send the data
response = device.read(64, timeout_ms=5000) # Read the resp. 5s timeout
if response[4] == 1: #prepare info for GUI
text = "On_off:\n"
text += "State: " + str(not state) + "\n"
self.queue_2_gui.put(text)
return response[4]
else:
print("Invalid response received")
return None
return response[4:14]
except Exception as e:
print(f"Error: {e}")
return None
finally:
device.close()
https://www.weigu.lu/other_projects/python_coding/using_threading/index.html