Python coding

Alientek DP100 power supply manipulator

last updated: 2025-05-22

Quick links

DP100_manipulator
click for a better view

Intro

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.

Data exchange over USB with hidapi

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

Protocol

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!

Command 0x10: Device Info

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.

Command 0x30: Basic Info

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.

The complicated command 0x35

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:

Get active 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!

Get all profiles

If we replace 0x80 with 0x000x09 we can read voset and ioset for all profiles.

Change a profile

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).

Activate a profile

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).

Switch ON and OFF

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.

Software DP100 Manipulator

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:

Running the program (Linux)

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

Libraries used

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.

Example of a function

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()

Downloads

Interesting links