Integrating UHF RFID Scanners: From BLE Connection to Production
UHF RFID (860–960 MHz) reads passive tags at distances up to 10 meters and hundreds of tags per second. Unlike HF NFC (up to 10 cm), this is essential for warehouse tasks. We often face situations where a client needs to pair a Zebra RFD40 or Chainway R6 with a mobile app for inventory or logistics. The typical pain points are not knowing which protocol to choose (LLRP or proprietary SDK), how to bypass frequency range limitations, and how to avoid performance loss. This article shares concrete experience and code for Android (Kotlin) that can be adapted for iOS and Flutter. We'll cover common mistakes and show working configurations using the Zebra RFD40 example.
How to Choose a Protocol: LLRP or Proprietary SDK?
LLRP (Low Level Reader Protocol, standard ISO 15961) is a universal protocol for controlling UHF readers. Supported by Impinj, Zebra, Alien. It allows using one code for different readers but requires a Wi-Fi connection to stationary equipment. LLRP is not suitable for mobile BLE readers—here you need a proprietary SDK.
Proprietary SDK—Zebra RFID SDK, Chainway SDK. Easier to integrate, works over BLE or USB, but ties you to a vendor. However, it gives access to the trigger button, power management, and real-time events.
| Characteristic | LLRP | Proprietary SDK (Zebra) |
|---|---|---|
| Connection | Wi-Fi / Ethernet | BLE / USB |
| Mobile readers | No | Yes (RFD40, TC21) |
| Cross‑platform | Yes | Only for SDK platform |
| Complexity | High | Medium |
| Integration speed | Week+ | 3–5 days |
For mobile UHF readers with BLE, we always recommend the proprietary SDK—it saves time and gives full control over the device.
Zebra RFID SDK: Practical Implementation
Let's look at integrating the Zebra RFD40 on Android using rfid-api3. The code below shows connection, power configuration, and EPC filtering.
implementation("com.zebra.rfid.api3:rfid-api3:2.0.2.109")
class UhfReaderManager(private val context: Context) : RfidEventsListener {
private var reader: RFIDReader? = null
private val readers = Readers(context, ENUM_TRANSPORT.BLUETOOTH)
fun connect(deviceName: String) {
val readerDevices = readers.GetAvailableRFIDReaderList()
val targetDevice = readerDevices?.find { it.name == deviceName } ?: return
reader = targetDevice.RFIDReader
reader?.connect()
// Configure read parameters
val params = reader?.Config?.antennaConfigurations?.get(0)
params?.transmitPowerIndex = 270 // ~30 dBm, maximum power
params?.receiveFrequency = 55 // optimal for most regions
reader?.Config?.setAntennaConfiguration(params)
// Events
reader?.Events?.apply {
addEventsListener(this@UhfReaderManager)
setHandheldEvent(true) // trigger button on reader
setTagReadEvent(true)
setInventoryStartEvent(true)
setInventoryStopEvent(true)
}
}
// Start inventory with EPC mask filter
fun startInventoryWithFilter(epcMask: String?) {
val startTrigger = TriggerInfo().apply {
startTrigger.triggerType = START_TRIGGER_TYPE.START_TRIGGER_TYPE_IMMEDIATE
}
val stopTrigger = TriggerInfo().apply {
stopTrigger.triggerType = STOP_TRIGGER_TYPE.STOP_TRIGGER_TYPE_DURATION
stopTrigger.stopTriggerByDuration.duration = 5000 // 5 seconds
}
val tagFilter = if (epcMask != null) {
TagFilter().apply {
tagPattern = epcMask
tagPatternBitCount = epcMask.length * 4
filterAction = FILTER_ACTION.FILTER_ACTION_STATE_AWARE_FILTERING_ACTION_UNSPECIFIED
}
} else null
reader?.Actions?.Inventory?.perform(startTrigger, stopTrigger, tagFilter)
}
override fun eventReadNotify(e: RfidReadEvents) {
e.ReadEventData.TagData.forEach { tag ->
onTagRead(
epc = tag.TagID,
rssi = tag.PeakRSSI.toInt(),
antennaId = tag.AntennaID.toInt(),
readCount = tag.ReadCount
)
}
}
override fun eventStatusNotify(rfidStatusEvents: RfidStatusEvents) {
when (rfidStatusEvents.StatusEventData.statusEventType) {
STATUS_EVENT_TYPE.INVENTORY_START_EVENT -> onInventoryStarted()
STATUS_EVENT_TYPE.INVENTORY_STOP_EVENT -> onInventoryCompleted()
STATUS_EVENT_TYPE.HANDHELD_TRIGGER_EVENT -> {
val triggerType = rfidStatusEvents.StatusEventData.HandheldTriggerEventData.handheldEvent
if (triggerType == HANDHELD_TRIGGER_EVENT_TYPE.HANDHELD_TRIGGER_PRESSED) {
startInventoryWithFilter(null)
}
}
}
}
}
transmitPowerIndex = 270 — power in reader units. Each manufacturer has its own scale. Zebra RFD40: 0–270 = 0–30 dBm. Excessive power in a closed warehouse causes reflections and false reads from adjacent areas.
How to Configure Regional Frequencies?
UHF RFID operates in different bands:
- USA/Canada (FCC): 902–928 MHz
- Europe/Russia (ETSI): 865–868 MHz
- China: 920–925 MHz
The mobile app must set the reader region:
reader?.Config?.setRegulatoryConfig(
RegulatoryConfig().apply {
region = REGULATORY_REGION.REGULATORY_REGION_ETSI // or FCC, China
enableHoppingChannels = true
}
)
Using the FCC band in Russia violates local radio frequency regulations. Modern readers hardware‑lock the wrong region, but we always verify compliance before launch.
Performance: 500 Tags/Second Without Lag
The tag stream must not be processed on the main thread—UI will freeze. Use coroutines and channels:
private val tagChannel = Channel<TagReadData>(capacity = Channel.UNLIMITED)
// In BroadcastReceiver/callback of reader — just put into channel
override fun eventReadNotify(e: RfidReadEvents) {
e.ReadEventData.TagData.forEach { tag ->
tagChannel.trySend(tag) // non‑blocking, no exception
}
}
// Processing in a separate coroutine
fun processTagsInBackground() {
scope.launch(Dispatchers.Default) {
for (tag in tagChannel) {
val epc = tag.TagID
inventoryRepository.recordRead(epc, tag.PeakRSSI.toInt())
}
}
}
This approach guarantees a responsive UI even under peak load. On iOS, a similar result is achieved with an OperationQueue and maxConcurrentOperationCount = 1.
Integration Process
- Requirements analysis — choose reader (Zebra, Chainway, Bluebird), define scenarios (inventory, receiving, shipping).
- SDK preparation — obtain keys, set up environment (Android Studio, Xcode).
- Communication module development — BLE connection, event handling, filtering.
- Testing with real tags — verify range, power, regional settings.
- Performance optimization — background threads, buffering, server sync.
- Deploy to App Store / Google Play + TestFlight for beta testing.
What's Included
- Connection and configuration of the selected UHF reader
- Read implementation with EPC and RSSI filtering
- Trigger and inventory event handling
- Regional frequency support (FCC/ETSI/China)
- Performance optimization (background threads, buffering)
- Integration with your WMS/ERP via REST or GraphQL
- Source code and documentation provided
- One month of free support after delivery
Timelines
Basic integration of one BLE reader with tag display — from 5 days. Extended solution with regional settings, synchronization, and filtering — 1–2 weeks. We'll assess your project for free — contact us to discuss details.
Why Trust Our Experience
We have completed over 50 projects with UHF RFID on mobile platforms, including integrations with 1C, SAP, and WMS systems. Our engineers hold Android and iOS certifications and have protocol‑level Bluetooth LE experience. We guarantee post‑delivery support — fixes and improvements within a month.







