שילוב קוראי UHF RFID: מחיבור BLE ועד לייצור
UHF RFID (860–960 מגה-הרץ) קורא תגיות פסיביות במרחקים של עד 10 מטרים ומאות תגיות בשנייה. בניגוד ל-HF NFC (עד 10 ס"מ), זה חיוני למשימות מחסן. לעיתים קרובות אנו נתקלים במצבים שבהם לקוח צריך לחבר Zebra RFD40 או Chainway R6 לאפליקציה ניידת לניהול מלאי או לוגיסטיקה. נקודות הכאב האופייניות הן אי-ידיעה באיזה פרוטוקול לבחור (LLRP או SDK קנייני), כיצד לעקוף מגבלות טווח תדרים, וכיצד להימנע מאובדן ביצועים. מאמר זה חולק ניסיון קונקרטי וקוד עבור Android (Kotlin) שניתן להתאים ל-iOS ו-Flutter. נסקור טעויות נפוצות ונראה תצורות עבודה באמצעות דוגמת Zebra RFD40.
כיצד לבחור פרוטוקול: LLRP או SDK קנייני?
LLRP (Low Level Reader Protocol, תקן ISO 15961) הוא פרוטוקול אוניברסלי לשליטה בקוראי UHF. נתמך על ידי Impinj, Zebra, Alien. הוא מאפשר שימוש בקוד אחד עבור קוראים שונים אך דורש חיבור Wi-Fi לציוד נייח. LLRP אינו מתאים לקוראי BLE ניידים—כאן יש צורך ב-SDK קנייני.
SDK קנייני—Zebra RFID SDK, Chainway SDK. קל יותר לשילוב, עובד דרך BLE או USB, אך קושר אותך לספק. עם זאת, הוא נותן גישה לכפתור ההדק, ניהול צריכת חשמל, ואירועים בזמן אמת.
| מאפיין | LLRP | SDK קנייני (Zebra) |
|---|---|---|
| חיבור | Wi-Fi / Ethernet | BLE / USB |
| קוראים ניידים | לא | כן (RFD40, TC21) |
| רוחב פלטפורמות | כן | רק לפלטפורמת ה-SDK |
| מורכבות | גבוהה | בינונית |
| מהירות שילוב | שבוע+ | 3–5 ימים |
עבור קוראי UHF ניידים עם BLE, אנו תמיד ממליצים על ה-SDK הקנייני—הוא חוסך זמן ונותן שליטה מלאה על המכשיר.
Zebra RFID SDK: יישום מעשי
בואו נסתכל על שילוב Zebra RFD40 ב-Android באמצעות rfid-api3. הקוד שלהלן מציג חיבור, הגדרת עוצמה, וסינון EPC.
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()
// Настройка параметров считывания
val params = reader?.Config?.antennaConfigurations?.get(0)
params?.transmitPowerIndex = 270 // ~30 dBm, максимальная мощность
params?.receiveFrequency = 55 // оптимально для большинства регионов
reader?.Config?.setAntennaConfiguration(params)
// События
reader?.Events?.apply {
addEventsListener(this@UhfReaderManager)
setHandheldEvent(true) // кнопка триггера на ридере
setTagReadEvent(true)
setInventoryStartEvent(true)
setInventoryStopEvent(true)
}
}
// Запуск инвентаризации с фильтрацией по маске EPC
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 секунд
}
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)
}
}
}
}
}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() // Настройка параметров считывания val params = reader?.Config?.antennaConfigurations?.get(0) params?.transmitPowerIndex = 270 // ~30 dBm, максимальная мощность params?.receiveFrequency = 55 // оптимально для большинства регионов reader?.Config?.setAntennaConfiguration(params) // События reader?.Events?.apply { addEventsListener(this@UhfReaderManager) setHandheldEvent(true) // кнопка триггера на ридере setTagReadEvent(true) setInventoryStartEvent(true) setInventoryStopEvent(true) } } // Запуск инвентаризации с фильтрацией по маске EPC 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 секунд } 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) } } } } } — עוצמה ביחידות קורא. לכל יצרן יש סולם משלו. Zebra RFD40: 0–270 = 0–30 dBm. עוצמה מוגזמת במחסן סגור גורמת להחזרים וקריאות שגויות מאזורים סמוכים.
כיצד להגדיר תדרים אזוריים?
UHF RFID פועל בפסים שונים:
- ארה"ב/קנדה (FCC): 902–928 מגה-הרץ
- אירופה/רוסיה (ETSI): 865–868 מגה-הרץ
- סין: 920–925 מגה-הרץ
האפליקציה הניידת חייבת להגדיר את אזור הקורא:
reader?.Config?.setRegulatoryConfig(
RegulatoryConfig().apply {
region = REGULATORY_REGION.REGULATORY_REGION_ETSI // или FCC, China
enableHoppingChannels = true
}
)שימוש בפס FCC ברוסיה מפר תקנות תדרי רדיו מקומיות. קוראים מודרניים נועלים חומרתית אזור שגוי, אך אנו תמיד בודקים עמידה בדרישות לפני השקה.
ביצועים: 500 תגיות/שנייה ללא פיגור
אין לעבד את זרם התגיות על ה-thread הראשי—ממשק המשתמש יקפא. השתמשו ב-coroutines ו-channels:
private val tagChannel = Channel<TagReadData>(capacity = Channel.UNLIMITED)
// В BroadcastReceiver/callback ридера — только кладём в канал
override fun eventReadNotify(e: RfidReadEvents) {
e.ReadEventData.TagData.forEach { tag ->
tagChannel.trySend(tag) // не блокирует, не бросает исключение
}
}
// Обработка в отдельной корутине
fun processTagsInBackground() {
scope.launch(Dispatchers.Default) {
for (tag in tagChannel) {
val epc = tag.TagID
inventoryRepository.recordRead(epc, tag.PeakRSSI.toInt())
}
}
}גישה זו מבטיחה ממשק משתמש מגיב גם בעומס שיא. ב-iOS, תוצאה דומה מושגת עם OperationQueue ו-transmitPowerIndex = 270.
תהליך השילוב
- ניתוח דרישות — בחירת קורא (Zebra, Chainway, Bluebird), הגדרת תרחישים (מלאי, קבלה, משלוח).
- הכנת SDK — השגת מפתחות, הגדרת סביבה (Android Studio, Xcode).
- פיתוח מודול תקשורת — חיבור BLE, טיפול באירועים, סינון.
- בדיקה עם תגיות אמיתיות — אימות טווח, עוצמה, הגדרות אזור.
- אופטימיזציית ביצועים — threads ברקע, חציצה, סנכרון שרת.
- פריסה ל-App Store / Google Play + TestFlight לבדיקות בטא.
מה כלול
- חיבור והגדרה של קורא ה-UHF הנבחר
- יישום קריאה עם סינון EPC ו-RSSI
- טיפול באירועי הדק ומלאי
- תמיכה בתדרים אזוריים (FCC/ETSI/סין)
- אופטימיזציית ביצועים (threads ברקע, חציצה)
- שילוב עם WMS/ERP שלך דרך REST או GraphQL
- קוד מקור ותיעוד מסופקים
- חודש תמיכה חינם לאחר המסירה
לוחות זמנים
שילוב בסיסי של קורא BLE אחד עם הצגת תגיות — מ-5 ימים. פתרון מורחב עם הגדרות אזור, סנכרון וסינון — 1–2 שבועות. נבחן את הפרויקט שלך — צור קשר לדיון בפרטים.
למה לסמוך על הניסיון שלנו
השלמנו למעלה מ-50 פרויקטים עם UHF RFID בפלטפורמות ניידות, כולל שילובים עם 1C, SAP ומערכות WMS. המהנדסים שלנו מחזיקים בהסמכות Android ו-iOS ויש להם ניסיון ברמת פרוטוקול ב-Bluetooth LE. אנו מבטיחים תמיכה לאחר המסירה — תיקונים ושיפורים בתוך חודש.







