Somewhere in the supply chain between a UHF module factory and a marketplace listing, branding and documentation get stripped out. What arrives is a reader with a working antenna, a CD with a Windows-only demo app, a .aar file that either doesn't compile or exposes no obvious inventory method, and a PDF entirely in Chinese. This is the guide for that box.
What the vendor docs don't tell you
There is no vendor to ask, which is the actual problem this guide solves. The information exists, but it's distributed across the physical device markings, the chipset's public FCC/CMIIT filing, and a small set of known module vendor SDK families that most white-label hardware is built on top of.
What the docs don't tell you
Most white-label UHF handhelds sold through general marketplaces are not custom hardware — they're a reference design from one of a small number of UHF module manufacturers (chipsets from vendors like Impinj, or module integrators building around them), repackaged with a different enclosure and a demo app slapped on top. The .aar bundled on the CD frequently targets an older Android API level than the device's own OS, which is why it fails to compile without modification rather than because the SDK itself is broken.
Step 1: Identify the actual module
Before touching any code, physically inspect the device and its regulatory markings.
- Look for an FCC ID or CMIIT ID sticker on the device housing or inside the battery compartment. Search that ID against the FCC ID database (fcc.report or the FCC's own equipment authorization search) — the filing often includes internal photos that reveal the actual module manufacturer's markings.
- Open the
.aar(it's a zip archive) and inspectAndroidManifest.xmland any.sonative libraries insidejni/. Native library filenames (e.g.,libuhf_xxx.so) frequently reference the actual module vendor's naming convention even when the wrapper Java/Kotlin classes were renamed for white-labeling. - Check the CD's demo app for an "About" or version string — Windows demo apps are less commonly rebranded than the mobile SDK and often retain the original module vendor's name in a splash screen or window title.
Step 2: Decompile the .aar with jadx
If the .aar doesn't compile as shipped, decompiling it reveals the real class and method structure, independent of whatever is broken in the build configuration.
# Unzip the .aar to get classes.jar
unzip reader-sdk.aar -d reader-sdk-extracted
cd reader-sdk-extracted
# Decompile with jadx
jadx classes.jar -d decompiled/Inspect decompiled/ for the actual package structure. Look specifically for:
- A class with
init,open, orconnectmethods taking a serial port path or Bluetooth MAC address — this is almost always the entry point. - Method names resembling
inventory,readTag,getEPC— these map to the read loop you'll eventually wire up. - Any hardcoded baud rate constants inside the native bridge classes — these tell you what serial configuration the module actually expects, without needing to guess.
Step 3: Probe the serial port directly if no usable SDK surfaces
Some white-label readers expose nothing usable in the .aar at all — in that case, treat the device as a raw serial peripheral and speak to it directly.
// Requires the device expose a usable serial path, e.g. via USB host mode
val port = "/dev/ttyUSB0"
val baudRates = listOf(115200, 9600, 57600)
fun probePort(path: String, baud: Int): Boolean {
val process = ProcessBuilder("stty", "-F", path, baud.toString()).start()
process.waitFor()
val inventoryCommand = byteArrayOf(0xA0.toByte(), 0x03, 0x01, 0x00, 0x00) // vendor-specific framing, verify against captured traffic
// Send inventoryCommand over the port, read response, check for EPC Gen2-style framing
return true // placeholder for actual response validation
}Most consumer-facing UHF modules speak a binary framed protocol over UART — typically a header byte, a length field, a command byte, a payload, and a checksum. If a public datasheet can't be found for the identified module, capturing traffic between the CD's Windows demo app and the reader (via a USB protocol analyzer, or by intercepting the demo app's serial calls) is the most reliable way to reconstruct the framing without documentation.
Step 4: Common SDK families to try before reverse-engineering from scratch
Before spending days on raw protocol reverse-engineering, check whether the module matches one of the SDK families that show up repeatedly across white-label UHF hardware:
- Impinj Indy-based modules, which sometimes ship with a recognizable register-level API even under a rebranded wrapper.
- Chainway-compatible UART framing — some white-label modules mimic the same command structure documented in Chainway's SDKs closely enough that the framing (not the Java wrapper) lines up directly.
- Generic "YR" / "YM" prefixed module families common in Shenzhen-area UHF module manufacturing, which share a command set across multiple resellers even when the branding differs.
Production considerations
- Document everything discovered during this process — the FCC ID, the identified module family, the decompiled class names, the working serial configuration — in a form the next engineer can use without repeating the reverse-engineering work.
- Treat a reverse-engineered integration as inherently less stable across firmware batches than a documented SDK. Pin the specific firmware/hardware revision this integration was validated against, and re-verify before assuming a new batch behaves identically.
- Weigh replacement cost against integration time realistically. Below a certain unit count, a known-brand reader with a documented SDK is often cheaper in total engineering time than reverse-engineering an unbranded unit, even though the unbranded hardware itself is cheaper.
Common errors
| Error | Cause | Fix |
|---|---|---|
.aar fails to build with a manifest merge error | Bundled SDK targets an incompatible API level or has a conflicting package name | Extract and repackage the .aar with a corrected minSdkVersion, or use jadx to extract only the needed classes into your own module |
| Serial port opens but returns garbage | Wrong baud rate assumed | Systematically try 115200, 9600, 57600, comparing response framing against expected EPC Gen2 binary structure |
| No response from the device at all over USB host mode | Device requires a specific USB vendor/product ID filter or permission intent not yet granted | Check UsbManager device enumeration and confirm the app has requested and received USB permission for that specific vendor/product ID |
Decompiled classes reference native .so methods with no visible implementation | Actual protocol logic lives in a native library, not the Java/Kotlin wrapper | Use a native disassembler (e.g., Ghidra) on the .so if the JNI bridge alone doesn't reveal the framing, or fall back to traffic capture against the CD demo app |