Implementing Asset Tracking via RFID in Mobile Applications
Asset Tracking via RFID differs from inventory. Inventory answers "what's on warehouse now." Asset Tracking answers "where was asset all this time, who moved it, and when." Requires event history, binding to reading points (gate antennas, stationary readers), and mobile app as verification and selective search tool.
Event-Driven Architecture
Each tag read — event with metadata:
data class AssetReadEvent(
val epc: String,
val readerLocation: String, // antenna/reader ID
val timestamp: Long,
val rssi: Int, // signal: approximate distance
val direction: ReadDirection?, // ENTRY / EXIT for gate readers
val operatorId: String? // who did manual read
)
enum class ReadDirection { ENTRY, EXIT, UNKNOWN }
Mobile app generates events with operatorId and GPS/indoor coordinates. Gate readers (Impinj Speedway, Zebra FX9600) generate events via LLRP or REST API. All converge to single event queue on backend.
Finding Specific Asset
Most frequent operation in mobile app — "find asset XYZ on this warehouse." RFID reader switches to "proximity search" mode: shows RSSI of specific tag, helping narrow search zone:
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) // read only target tag
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.5m
rssi > -65 -> ProximityLevel.CLOSE // 0.5–1.5m
rssi > -75 -> ProximityLevel.MEDIUM // 1.5–3m
else -> ProximityLevel.FAR // > 3m
}
}
EPC filter (setInventoryFilter) is critical — without it, reader reads all tags in range and clogs data stream. Specific filtering APIs depend on reader SDK: Zebra RFID SDK — SLFlag, TagFilter; Chainway SDK — FilterParam.
Movement History
// Room entities for asset history
@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>
}
Device history — only recent N events. Full history — on server. WorkManager syncs unsynced events on network recovery.
ERP/WMS Integration
Asset Tracking without accounting system integration — half solution. REST API for synchronization:
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 — for verification: operator scans tag, app immediately shows where system last saw it. Mismatch between actual and system location — red flag for logistician.
Timeline
Asset search mobile app + event history + REST sync with WMS: 5 days. Complete solution including Impinj/Zebra stationary reader setup, LLRP integration, and server event bus: 2–4 weeks.







