Реализация отслеживания активов (Asset Tracking) через RFID в мобильном приложении
Asset Tracking через RFID — это не инвентаризация. Инвентаризация отвечает на вопрос «что есть на складе прямо сейчас». Asset Tracking отвечает на вопрос «где был актив всё это время, кто его перемещал и когда». Для этого нужна история событий, привязка к точкам считывания (воротные антенны, стационарные ридеры) и мобильное приложение как один из инструментов верификации и выборочного поиска.
Архитектура событийной модели
Каждое считывание тега — событие с метаданными:
data class AssetReadEvent(
val epc: String,
val readerLocation: String, // ID антенны/ридера
val timestamp: Long,
val rssi: Int, // сигнал: приблизительная дистанция
val direction: ReadDirection?, // ENTRY / EXIT для воротных ридеров
val operatorId: String? // кто сделал ручное считывание
)
enum class ReadDirection { ENTRY, EXIT, UNKNOWN }
Мобильное приложение генерирует события с operatorId и GPS/indoor-координатами. Воротные ридеры (Impinj Speedway, Zebra FX9600) генерируют свои события через LLRP или REST API. Всё сходится в одну очередь событий на бэкенде.
Поиск конкретного актива
Самая частая операция в мобильном приложении — «найди актив XYZ на этом складе». RFID-ридер переходит в режим «proximity search»: показывает RSSI конкретного тега, помогая сократить зону поиска:
class AssetSearchSession(
private val rfidReader: RfidReader,
private val targetEpc: String
) {
private val _proximity = MutableStateFlow(ProximityLevel.UNKNOWN)
val proximity: StateFlow<ProximityLevel> = _proximity.asStateFlow()
fun start() {
rfidReader.setInventoryFilter(epcFilter = targetEpc) // читать только целевой тег
rfidReader.startContinuousInventory(onTag = { tag ->
if (tag.epc == targetEpc) {
_proximity.value = rssiToProximity(tag.peakRSSI)
}
})
}
private fun rssiToProximity(rssi: Int): ProximityLevel = when {
rssi > -55 -> ProximityLevel.VERY_CLOSE // < 0.5м
rssi > -65 -> ProximityLevel.CLOSE // 0.5–1.5м
rssi > -75 -> ProximityLevel.MEDIUM // 1.5–3м
else -> ProximityLevel.FAR // > 3м
}
}
Фильтр по EPC (setInventoryFilter) критичен — без него ридер читает все теги в зоне и засоряет поток данными. Конкретные API фильтрации зависят от SDK ридера: Zebra RFID SDK — SLFlag, TagFilter; Chainway SDK — FilterParam.
История перемещений
// Room entities для истории активов
@Entity(tableName = "asset_events", indices = [Index("epc"), Index("timestamp")])
data class AssetEventEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val epc: String,
val eventType: String, // "scan", "checkpoint", "entry", "exit"
val locationId: String,
val locationName: String,
val operatorId: String?,
val rssi: Int?,
val timestamp: Long,
val synced: Boolean = false
)
@Dao
interface AssetEventDao {
@Query("SELECT * FROM asset_events WHERE epc = :epc ORDER BY timestamp DESC LIMIT 50")
fun getAssetHistory(epc: String): Flow<List<AssetEventEntity>>
@Query("SELECT * FROM asset_events WHERE synced = 0 ORDER BY timestamp ASC")
suspend fun getUnsynced(): List<AssetEventEntity>
}
История на устройстве — только последние N событий. Полная история — на сервере. WorkManager синхронизирует несинхронизированные события при появлении сети.
Интеграция с ERP/WMS
Asset Tracking без интеграции с учётной системой — половина решения. REST API для синхронизации:
interface AssetTrackingApi {
@POST("events/batch")
suspend fun pushEvents(@Body events: List<AssetEventDto>): Response<BatchResult>
@GET("assets/{epc}")
suspend fun getAssetInfo(@Path("epc") epc: String): Response<AssetInfoDto>
@GET("assets/{epc}/location")
suspend fun getLastKnownLocation(@Path("epc") epc: String): Response<LocationDto>
}
getLastKnownLocation — для верификации: оператор сканирует тег, приложение сразу показывает где система его видела последний раз. Расхождение реального и системного местоположения — красный флаг для логиста.
Сроки
Мобильное приложение поиска активов + история событий + REST-синхронизация с WMS: 5 дней. Полное решение включая настройку стационарных ридеров Impinj/Zebra, LLRP-интеграцию и серверную событийную шину: 2–4 недели.







