Реализация инвентаризации через RFID-сканер в мобильном приложении

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.
Разработка и поддержка любых видов мобильных приложений:
Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Предлагаемые услуги
Показано 1 из 1 услугВсе 1735 услуг
Реализация инвентаризации через RFID-сканер в мобильном приложении
Средняя
~5 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1054
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    445

Реализация инвентаризации через RFID-сканер в мобильном приложении

RFID-инвентаризация в отличие от штрихкода — массовое одновременное считывание. Склад прогнал ридером вдоль стеллажа и за 3 секунды считал 200 тегов. Задача мобильного приложения: принять этот поток EPC-кодов без потерь, дедуплицировать (один тег может попасть в несколько reads), сверить с ожидаемым списком и показать расхождения. Кажется просто — пока не столкнёшься с дубликатами, тегами вне сессии и необходимостью работать офлайн.

Архитектура RFID-инвентаризации

Сессия инвентаризации — конечный автомат с чёткими переходами:

IDLE → SCANNING → PROCESSING → COMPLETED
              ↓
           PAUSED → SCANNING

На каждый прочитанный тег — update в MutableStateFlow с дедупликацией по EPC:

class InventorySession(private val expectedItems: List<InventoryItem>) {
    private val _scannedEpcs = MutableStateFlow<Set<String>>(emptySet())
    val scannedEpcs: StateFlow<Set<String>> = _scannedEpcs.asStateFlow()

    // Производные состояния
    val matchedItems = scannedEpcs.map { epcs ->
        expectedItems.filter { it.epc in epcs }
    }.stateIn(scope, SharingStarted.Eagerly, emptyList())

    val missingItems = scannedEpcs.map { epcs ->
        expectedItems.filter { it.epc !in epcs }
    }.stateIn(scope, SharingStarted.Eagerly, emptyList())

    val unexpectedEpcs = scannedEpcs.map { epcs ->
        val knownEpcs = expectedItems.map { it.epc }.toSet()
        epcs.filter { it !in knownEpcs }
    }.stateIn(scope, SharingStarted.Eagerly, emptyList())

    fun onTagRead(epc: String) {
        _scannedEpcs.update { current -> current + epc }
    }

    fun reset() {
        _scannedEpcs.value = emptySet()
    }
}

Set<String> — автоматическая дедупликация. Один EPC может прийти 50+ раз за одну инвентаризацию (ридер сканирует на высокой скорости), но в Set попадёт один раз.

Отображение результатов в реальном времени

LazyColumn с key(item.epc) — анимированное добавление найденных позиций:

@Composable
fun InventoryResultsScreen(session: InventorySession) {
    val matched by session.matchedItems.collectAsState()
    val missing by session.missingItems.collectAsState()
    val scanned by session.scannedEpcs.collectAsState()

    Column {
        // Прогресс: X из Y найдено
        LinearProgressIndicator(
            progress = { if (session.expectedItems.isEmpty()) 0f
                        else matched.size.toFloat() / session.expectedItems.size }
        )
        Text("Найдено: ${matched.size}/${session.expectedItems.size}")

        LazyColumn {
            items(matched, key = { it.epc }) { item ->
                InventoryItemRow(item = item, status = ItemStatus.FOUND)
            }
            items(missing, key = { it.epc }) { item ->
                InventoryItemRow(item = item, status = ItemStatus.MISSING)
            }
        }
    }
}

Офлайн-режим и синхронизация

Склад часто без Wi-Fi. Локальная БД через Room — хранит ожидаемый список и результаты:

@Entity(tableName = "inventory_sessions")
data class InventorySessionEntity(
    @PrimaryKey val sessionId: String,
    val locationId: String,
    val startedAt: Long,
    val completedAt: Long?,
    val status: String // "in_progress", "completed", "synced"
)

@Entity(tableName = "scanned_tags")
data class ScannedTagEntity(
    @PrimaryKey val epc: String,
    val sessionId: String,
    val firstSeenAt: Long,
    val readCount: Int
)

readCount — количество reads одного тега за сессию. Аномально низкий (1–2) при том что соседние теги читались 20+ раз — признак плохого физического расположения тега или повреждения. Полезная метрика для QA.

После завершения сессии — синхронизация через WorkManager при появлении сети:

val syncRequest = OneTimeWorkRequestBuilder<InventorySyncWorker>()
    .setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
    .setInputData(workDataOf("session_id" to sessionId))
    .build()
workManager.enqueueUniqueWork("sync_$sessionId", ExistingWorkPolicy.KEEP, syncRequest)

GS1 EPC декодирование

EPC — это не просто hex-строка. Это структурированный код: urn:epc:id:sgtin:0614141.107346.2017 (SGTIN — Serialized GTIN, глобальный торговый номер с серийным номером). Декодирование через GS1 EPC Information Services:

// SGTIN-96 декодирование (наиболее распространённый формат)
fun decodeSgtin96(epc: String): Sgtin96? {
    val bytes = epc.chunked(2).map { it.toInt(16) }.toByteArray()
    val bits = BigInteger(1, bytes)

    val header = bits.shiftRight(88).and(BigInteger.valueOf(0xFF)).toInt()
    if (header != 0x30) return null // Не SGTIN-96

    val filter = bits.shiftRight(85).and(BigInteger.valueOf(0x07)).toInt()
    val partition = bits.shiftRight(82).and(BigInteger.valueOf(0x07)).toInt()
    // ... далее по partition table: company prefix + item reference + serial
}

Готовая библиотека: com.gs4tr.epcis:epcis-rest-client или org.fosstrak.epcis:epcis-repository-client.

Сроки

Мобильное приложение инвентаризации с Zebra/кастомным BLE-ридером, офлайн Room, GS1 декодированием и синхронизацией: 5 дней (простой склад, один ридер, один тип тегов) до 2–3 недель (multi-location, несколько типов тегов, кастомная EPC-схема, REST-интеграция с WMS).