Reverse Engineering a UART Capacitive Touch Sensor Module and Using Python to Control - SEN0542

by alaminashik in Circuits > Microcontrollers

23 Views, 0 Favorites, 0 Comments

Reverse Engineering a UART Capacitive Touch Sensor Module and Using Python to Control - SEN0542

Cover.jpg
pressing finger analuzer.gif

I recently got this SEN0542, a very slim and very accurate fingerprint sensor from DFRobot, which has an onboard processor for detecting, storing, and learning fingerprints. It comes with software that can be used to communicate with the module and perform a ton of functions. But, it is only an .exe file! So, I took the opportunity to reverse engineer the sensors and wrote multiple Python scripts that can be used to directly communicate with the sensor with/without any MCU, and in this article, I will share all my findings!

This sensor uses UART (Universal Asynchronous Receiver-Transmitter) communication, which is one of the most basic forms of communication in embedded systems, so I tried to capture the data that is being transferred between the sensor and the software, and used AI to write small Python scripts to perform all the basic operations.


Note: A very well-documented use of this module via Arduino is given on the official page, but the library is huge and takes up a lot of storage for features that are not readily needed. LINK

Supplies

supplies 3.png
supplies 2.png
supplies 1.jpg

1 x UART Capacitive Fingerprint Sensor LINK

1 x 3.3V USB-to-TTL Module LINK

1 x USB Saleae logic analyzer LINK

1 x Bread Board LINK

Few jumper wires LINK

Clear Some Basics

uart comm.png

UART is a hardware communication protocol that is used to transfer data between two devices via only two wires. One wire is for transmitting data (Tx) and another for receiving (RX). It is asynchronous, as it has no clock signal wire like other protocols (i.e., I2C, SPI)

For UART, the baud rate needs to be set the same on both the transmitting and receiving devices for successful communication. The baud rate is the rate at which information is transferred to a communication channel. In our case, the sensor can be set to run at different baud rates, but the default is 115200.

We can thus capture the signals at a 115200 baud rate. that are being transmitted via the RX, TX lines, and find out what is being transferred and received during an operation. The TX pin of the sensor sends data to the computer, and vice versa.

To learn more about UART communication, check out this link from Analog Devices: LINK

Connect the Sensor With the Computer

connection diagram.jpg

To trap the communication, I used a USB to TTL by Reyax technology, module set at 3.3V and the software that comes with the sensor. This way, the sensor can directly send and receive data to the computer via serial communication (this is required by the Chinese software)

The connections are:

Sensor ---> USB-to-TTL

  1. 3V3 ---> 3.3V
  2. TX ---> RX
  3. RX ---> TX
  4. GND ---> GND
  5. VIN ---> 3.3V

The VIN button is connected to keep the sensor awake; otherwise, it will move to sleep mode.

After connecting the sensor, we will set up the software that comes with the sensor and perform some basic tasks in the next steps.

Using the "Chinese" Software (testing)

get enrolled id list.png
comm success.png
initial page connect.png
initial page.png
success verification.png
enroll initialization success.png

The software is very neat, connects to the sensor easily, and has a lot of features.

Just select the proper COM port, set the baud rate to 115200, and hit connect. If the connection is not successful, an error prompt will appear. Check the connection and COM port.

I have added several images above to show how to use the software. Some useful notes:

  1. To enroll a new finger, select a unique ID. To find the stored IDs, press the "Get Enrolled ID list" button.
  2. Use a minimum of 2 enroll count while enrolling a new finger for reliable detection.
  3. Enabling the 'show image' takes a significantly longer time to process each fingerprint.
  4. An error while connecting with the sensor usually means the wrong COM port was selected.

Software link: LINK

Now, let's hack the system in the next step.

Using the Logic Analyzer to Capture

connection logic ana.jpg
speed rate.jpg
RXTXSI~1.PNG
parameters of uart.png

I used a cheap Saleae logic analyzer to record the data transmission at a baud rate of 115200. The Logic software provides useful options to directly analyze and visualize data in formats like bin, hex, decimal, etc.

Software link: https://saleae.com/downloads


Connections:

Analyzer ---> Sensor

  1. CH1 ---> RX
  2. CH2 ---> TX

The logic analyzer does not require a ground connection, as the analyzer, sensor, and USB-to-TTL are all connected to the same computer. This connection allows us to see what the software is sending (white graph) and what the sensor is sending (brown graph) simultaneously.


To use the Logic software:

  1. Open the software.
  2. Connect the logic analyzer using USB port.
  3. From the device settings, set the speed to around 200 kS/s (200 kilo Samples per second)
  4. Press "R" to start recording the capture. You will see the graph update when you use the fingerprint software.


To analyze data, we need to know that the sensor takes in 26 packets of data from the software at a baud rate of 115200, 8 bits per transfer, 1-stop bit, no parity bit, and the least significant bit is sent first. Don't worry, these are usually default parameters in all commercial devices :). but these info are useful while analyzing data from the Saleae software, as shown in the second last picture above.

Analyzing and Writing Python Scripts

data bytes.png
enrollment communication bytes.png

Analyzing the data was not an easy task. Let's look at an example of enrolling a fingerprint. The first image above shows the whole communication that takes place between the software and the sensor. Each of these lines contains 26 packets of data. These packets are what we need!

I carefully wrote the whole data transmission byte by byte at every point of the enrollment process, and finally came up with 25 different packets of data. I fed this info to AI and asked it to write code to replicate this data.

The enrollment process is probably the hardest, and it works as follows:

  1. Begin enrollment ID: 0x46
  2. Get Finger Image ID: 0x20
  3. Get finger present/released ID:0x21
  4. Generate template into buffer ID:0x60
  5. Merge templates ID:0x61
  6. Save templates ID:0x40
  7. End transmission ID:0x24

In this same way, I captured and noted the other basic operations of the fingerprint sensor and used AI to generate code snippets.

Finally the Python Scripts

delete an id.jpg
enrolling new finger.png
verify stored fingerprints.png
stored fingerprints.png

Finally, ChatGPT and Grok both helped to generate several Python codes that can be used to directly perform various tasks, and after some adjustments and verifications, these codes work flawlessly.

In all the scripts:

  1. It defines the library, port, begins serial communication, and sets the baud rates.
  2. Defines a function "cmd" that generates the 26 packet of data with a given command.
  3. The 'cmd' function can also wait and receive the sensor data.
  4. After each data transmission prints relevant information.


Enrolling a new finger:

import serial
import time

# ================= CONFIG =================
PORT = 'COM3'
BAUD = 115200
DID = 0x00
# =========================================

ser = serial.Serial(PORT, BAUD, timeout=2)
time.sleep(0.5)

def cmd(cmd_id, data=b''):
pkt = bytearray(26)
pkt[0:4] = b'\x55\xAA\x00' + DID.to_bytes(1, 'little')
pkt[4:6] = cmd_id.to_bytes(2, 'little')
pkt[6:8] = len(data).to_bytes(2, 'little')
pkt[8:8+len(data)] = data
pkt[24:26] = (sum(pkt[:24]) & 0xFFFF).to_bytes(2, 'little')

ser.write(pkt)
resp = ser.read(26)

if len(resp) != 26 or resp[0:2] != b'\xAA\x55':
return 0xFFFF, b''

ret = int.from_bytes(resp[8:10], 'little')
ln = int.from_bytes(resp[6:8], 'little') - 2
return ret, resp[10:10+ln] if ln > 0 else b''


def wait_finger_present():
while True:
if cmd(0x0020)[0] == 0x0000:
return
time.sleep(0.2)


def wait_finger_removed():
while True:
if cmd(0x0021)[0] == 0x0000:
break
time.sleep(0.2)

while cmd(0x0020)[0] == 0x0000:
time.sleep(0.2)

time.sleep(0.6)


# ================= ENROLL =================
fid = int(input("Enter fingerprint ID (1–80): "))
print(f"\nEnrolling ID {fid}")

# Start enrollment
cmd(0x0046, fid.to_bytes(2, 'little'))

# -------- First finger --------
print("Place finger (1/3)...")
wait_finger_present()

if cmd(0x0060, b'\x00\x00')[0] != 0x0000:
print("First template failed")
exit()

print("Lift finger")
wait_finger_removed()

# -------- Second finger --------
print("Place same finger (2/3)...")
wait_finger_present()

if cmd(0x0060, b'\x01\x00')[0] != 0x0000:
print("Second template failed")
exit()

print("Lift finger")
wait_finger_removed()

# -------- Third finger --------
print("Place same finger (3/3)...")
wait_finger_present()

if cmd(0x0060, b'\x02\x00')[0] != 0x0000:
print("Third template failed")
exit()

print("Lift finger")
wait_finger_removed()

# -------- Merge (3 templates) --------
if cmd(0x0061, b'\x00\x00\x03\x00')[0] != 0x0000:
print("Template merge failed")
exit()

# -------- Store --------
if cmd(0x0040, fid.to_bytes(2, 'little') + b'\x00\x00')[0] != 0x0000:
print("Store failed")
exit()

# -------- End --------
cmd(0x0024, b'\x00\x00')

print("Enrollment successful (3-scan)")
ser.close()


Get Device ID:

import serial
import time

PORT = 'COM3'
BAUD = 115200
DID = 0x00

ser = serial.Serial(PORT, BAUD, timeout=1)
time.sleep(0.5)

def cmd(cmd_id, data=b''):
pkt = bytearray(26)
pkt[0:4] = b'\x55\xAA\x00' + DID.to_bytes(1, 'little')
pkt[4:6] = cmd_id.to_bytes(2, 'little')
pkt[6:8] = len(data).to_bytes(2, 'little')
pkt[8:8+len(data)] = data
pkt[24:26] = (sum(pkt[:24]) & 0xFFFF).to_bytes(2, 'little')

ser.write(pkt)
resp = ser.read(26)

if len(resp) != 26 or resp[0:2] != b'\xAA\x55':
return None

return resp

print("Device Information\n")

# ---- Device ID ----
resp = cmd(0x0024)
if resp:
print(f"Device ID Success ID : {resp[8]}")
else:
print("Device ID Failed")

ser.close()


Get Stored Fingerprints:

import serial
import time

SERIAL_PORT = 'COM3'
BAUD_RATE = 115200
DID = 0x01

ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
time.sleep(0.3)

def checksum(buf):
return sum(buf) & 0xFFFF

def cmd(cmd_id):
pkt = bytearray(26)
pkt[0:2] = b'\x55\xAA'
pkt[2] = DID
pkt[3] = 0x00
pkt[4:6] = cmd_id.to_bytes(2, 'little')
pkt[6:8] = (0).to_bytes(2, 'little')
pkt[24:26] = checksum(pkt[:24]).to_bytes(2, 'little')

ser.write(pkt)

# ACK frame
ack = ser.read(26)
if len(ack) != 26 or ack[0:2] != b'\xAA\x55':
return None

if int.from_bytes(ack[8:10], 'little') != 0:
return None

# DATA frame
time.sleep(0.07)
hdr = ser.read(8)
if len(hdr) != 8 or hdr[0:2] != b'\xA5\x5A':
return None

data_len = int.from_bytes(hdr[6:8], 'little')
data = ser.read(data_len + 2)

return data[:data_len]

# ---- Execute ----
payload = cmd(0x0049)

if payload is None or len(payload) < 4:
print("Failed to get ID list")
else:
enroll_count = int.from_bytes(payload[0:2], 'little')
bitmap = payload[2:]

enrolled_ids = []
for byte_index, b in enumerate(bitmap):
for bit in range(8):
if b & (1 << bit):
enrolled_ids.append(byte_index * 8 + bit)

print(f"Enroll Count = {enroll_count}")
print("ID =")
for i in enrolled_ids:
print(i)

ser.close()


Delete an ID:

import serial
import time

SERIAL_PORT = 'COM3' # Change to your USB-TTL port
BAUD_RATE = 115200
DID = 0x01 # Try 0x00 if no response

ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=2)
time.sleep(0.3)

def cmd(cmd_id, data=b''):
packet = bytearray(26)
packet[0:4] = b'\x55\xAA\x00' + DID.to_bytes(1, 'little')
packet[4:6] = cmd_id.to_bytes(2, 'little')
packet[6:8] = len(data).to_bytes(2, 'little')
packet[8:8+len(data)] = data
packet[24:26] = (sum(packet[:24]) & 0xFFFF).to_bytes(2, 'little')
ser.write(packet)
resp = ser.read(26)
if len(resp) < 26 or resp[0:2] != b'\xAA\x55':
return 0xFF, b''
ret = int.from_bytes(resp[8:10], 'little')
data_len = int.from_bytes(resp[6:8], 'little') - 2
return ret, resp[10:10+data_len] if data_len > 0 else b''

# Prompt for ID to delete
try:
fid = int(input("Enter the fingerprint ID to delete (1-80): "))
if not 1 <= fid <= 80:
print("ID must be between 1 and 80")
ser.close()
exit()
except:
print("Invalid input")
ser.close()
exit()

print(f"Attempting to delete single fingerprint ID: {fid}")

# Correct way to delete a single specific ID
del_data = fid.to_bytes(2, 'little') + fid.to_bytes(2, 'little') # startID = fid, endID = fid

ret, _ = cmd(0x0044, del_data)

if ret == 0x0000:
print(f"Successfully deleted ID {fid}")
elif ret == 0x0012:
print(f"Delete returned 0x0012: No fingerprint stored at ID {fid} (template empty)")
else:
print(f"Delete failed (error code: 0x{ret:04X})")

ser.close()


Delete All ID:

import serial
import time

SERIAL_PORT = 'COM3' # Change to your USB-TTL port
BAUD_RATE = 115200
DID = 0x01 # Try 0x00 if no response

ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=2)
time.sleep(0.3)

def cmd(cmd_id, data=b''):
packet = bytearray(26)
packet[0:4] = b'\x55\xAA\x00' + DID.to_bytes(1, 'little')
packet[4:6] = cmd_id.to_bytes(2, 'little')
packet[6:8] = len(data).to_bytes(2, 'little')
packet[8:8+len(data)] = data
packet[24:26] = (sum(packet[:24]) & 0xFFFF).to_bytes(2, 'little')
ser.write(packet)
resp = ser.read(26)
if len(resp) < 26 or resp[0:2] != b'\xAA\x55':
return 0xFF, b''
ret = int.from_bytes(resp[8:10], 'little')
return ret, b''

print("Deleting ALL stored fingerprints from the module...")

# Correct data for delete all: startID=1 (0x01 0x00), endID=80 (0x50 0x00)
del_all_data = b'\x01\x00\x50\x00'

ret, _ = cmd(0x0044, del_all_data)

if ret == 0x0000:
print("All fingerprints deleted successfully!")
elif ret == 0x0012:
print("No fingerprints were stored (module already empty)")
else:
print(f"Delete all failed (error code: 0x{ret:04X})")

ser.close()


You can find details about this code from my GitHub: LINK

Finish

pressing finger analuzer.gif

And that's how its done. You can now capture data from any device using this method and generate Python code to play with it.

If you don't know how to run a Python script, check out this link: https://realpython.com/run-python-scripts/

I hope you learned something by reading this article. Let me know in the comments. Cheers!