Інтеграція RFID-зчитувачів через Bluetooth у мобільне додаток
RFID-зчитувачі з BLE-інтерфейсом — це окремий клас пристроїв: Zebra RFD40/RFD90, Chainway R6, TSL 1128. Вони підключаються до телефонів через BLE SPP-профіль (Serial Port Profile) або користувацькі GATT-сервіси та транслюють прочитані EPC/UID-теги. Інтеграція простіша, ніж здається, якщо розуміти протокол конкретного зчитувача.
BLE SPP vs GATT
Більшість Bluetooth RFID-зчитувачів працюють у двох режимах:
BLE SPP (Serial Port Profile over BLE) — емулюють послідовний порт. Дані надходять через користувацькі GATT-характеристики з UUID виробника. Nordic UART Service (NUS) — найпоширеніший: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E.
Стандартний GATT — деякі зчитувачі реалізують власний GATT-сервіс з окремими характеристиками для команд та відповідей.
Для Zebra RFD40 — Nordic UART Service:
val NORDIC_UART_SERVICE = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
val NORDIC_UART_RX = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E") // запис
val NORDIC_UART_TX = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E") // сповіщення
fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
val service = gatt.getService(NORDIC_UART_SERVICE) ?: return
// Підписка на TX (отримання даних від зчитувача)
val txChar = service.getCharacteristic(NORDIC_UART_TX)
gatt.setCharacteristicNotification(txChar, true)
val descriptor = txChar.getDescriptor(CCCD_UUID)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
}
fun sendCommand(command: ByteArray) {
val rxChar = bluetoothGatt?.getService(NORDIC_UART_SERVICE)
?.getCharacteristic(NORDIC_UART_RX) ?: return
// Якщо команда > MTU-3 байти, потрібна фрагментація
if (command.size > negotiatedMtu - 3) {
sendFragmented(command)
} else {
rxChar.value = command
bluetoothGatt?.writeCharacteristic(rxChar)
}
}
Аналіз RFID-відповідей
Протокол залежить від зчитувача. Zebra RFD40 повертає ASCII-рядки у форматі EPC: <hex>\r\n. TSL 1128 використовує бінарні пакети з заголовком та CRC. Приклад для ASCII-протоколу:
private val dataBuffer = StringBuilder()
fun onCharacteristicChanged(value: ByteArray) {
dataBuffer.append(String(value, Charsets.UTF_8))
// Дані надходять фрагментами — чекаємо повного рядка
while (dataBuffer.contains('\n')) {
val lineEnd = dataBuffer.indexOf('\n')
val line = dataBuffer.substring(0, lineEnd).trim()
dataBuffer.delete(0, lineEnd + 1)
if (line.startsWith("EPC:")) {
val epc = line.removePrefix("EPC:").trim()
onTagRead(epc)
}
}
}
Критично: BLE-пакети можуть надходити розділеними — один рядок у кількох onCharacteristicChanged сповіщеннях. Завжди буферизуйте до розділювача (зазвичай \r\n).
Zebra SDK як альтернатива
Для пристроїв Zebra (RFD40, RFD90, RFD8500) — офіційний EMDK for Android або Zebra RFID SDK:
implementation("com.zebra.rfid.api3:rfid-api3:2.0.2.109")
val readers = Readers(context, ENUM_TRANSPORT.BLUETOOTH)
readers.GetAvailableRFIDReaderList()?.forEach { readerDevice ->
val reader = readerDevice.RFIDReader
reader.connect()
reader.Events.addEventsListener(rfidEventsListener)
reader.Events.setHandheldEvent(true)
reader.Events.setTagReadEvent(true)
reader.Actions.Inventory.perform() // почати безперервне сканування
}
private val rfidEventsListener = object : RfidEventsListener {
override fun eventReadNotify(e: RfidReadEvents) {
e.ReadEventData.TagData.forEach { tag ->
Log.d("RFID", "EPC: ${tag.TagID}, RSSI: ${tag.PeakRSSI}")
}
}
override fun eventStatusNotify(rfidStatusEvents: RfidStatusEvents) {
// STATUS_EVENT_BATTERY_LOW та інші коди статусу
}
}
RSSI від Zebra SDK надає приблизну дистанцію до тегу — корисно для пріоритизації при одночасному читанні кількох тегів.
Терміни
Інтеграція через Nordic UART Service або подібний користувацький GATT з аналізом ASCII-протоколу: 5 днів. Інтеграція через Zebra/Chainway SDK з розширеними функціями (інвентаризація, RSSI, запис у тег): 1–2 тижні.







