ברקודים סטנדרטיים דורשים קו ראייה וסריקה פריט-פריט—לא יעיל לניהול מלאי המוני. RFID מאפשר קריאת 200 תגיות בשלוש שניות רק על ידי הליכה לאורך מדף. אנו מפתחים אפליקציות מובייל שקולטות זרם של קודי EPC ללא אובדן, מבצעות דדופליקציה, ומתאמות מול הרשימה הצפויה—הכל בזמן אמת, אפילו ללא אינטרנט. הניסיון שלנו: 5 שנים בפיתוח מובייל, מעל 30 פרויקטים בלוגיסטיקה ומלאי.
לדוגמה, במחסן עם מדפי מתכת, תגיות UHF לעיתים קרובות נכשלות בקריאה עקב השתקפויות. אנו מכוונים פרמטרים של הקורא: עוצמה, קיטוב, מסננים. בפרויקט אחד, הפחתנו את שיעור הפספוס מ-15% ל-2% על ידי בחירת אנטנות והגדרות. זה חשוב במיוחד למשטחי מתכת שבהם הגדרות סטנדרטיות נכשלות.
בעיות שאנו פותרים
קריאות כפולות—תגית אחת יכולה להיקרא 50+ פעמים בסשן. נדרשת דדופליקציה מהירה ללא חסימת ממשק המשתמש. מצב לא מקוון—מחסנים לעיתים קרובות חסרי Wi-Fi. מסד הנתונים חייב להיות מקומי עם סנכרון מאוחר יותר. אי-התאמות—חלק מהתגיות עשויות שלא להיקרא עקב נזק או מיקום לקוי. אנו מציגים במפורש פריטים שנמצאו, חסרים, ומיותרים. זה קריטי למחסנים עם מדפי מתכת שבהם תגיות UHF מתפקדות גרוע יותר.
סריקת RFID היא מהירה פי 100 מהזנה ידנית או ברקודים בבדיקת מאות פריטים. אך ללא טיפול נכון בנתונים, יתרון זה אובד.
כיצד לבצע דדופליקציה של תגיות EPC ללא אובדן
סשן המלאי הוא מכונת מצבים עם מעברים: IDLE -> SCANNING -> PROCESSING -> COMPLETED, עם יכולת השהיה. עבור כל תגית שנקראה, אנו מעדכנים 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()
}
}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() } } מספק דדופליקציה אוטומטית. EPC אחד עשוי להגיע 50+ פעמים, אך ה-Set מאחסן אותו פעם אחת. מצבים נגזרים (נמצא, חסר, מיותר) מחושבים ריאקטיבית דרך Set<String>.
מדוע סנכרון לא מקוון הוא קריטי למחסנים
אנו מבטיחים שהאפליקציה עובדת ללא רשת. מסד נתונים מקומי של 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
)map הוא מספר הקריאות עבור תגית בודדת בסשן. ספירות נמוכות באופן חריג (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) כיצד להציג תוצאות בזמן אמת
@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—הוספה מונפשת של פריטים שנמצאו:
@Composable fun InventoryResultsScreen(session: InventorySession) {
val matched by session.matchedItems.collectAsState()
val missing by session.missingItems.collectAsState()
val scanned by session.scannedEpcs.collectAsState()
Column {
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)
}
}
}
} פענוח GS1 EPC
EPC הוא לא רק מחרוזת הקס. קוד מובנה כמו 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) מכיל חברה, מזהה פריט, ומספר סידורי. פענוח דרך SGTIN-96:
דוגמת קוד לפענוח 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
val filter = bits.shiftRight(85).and(BigInteger.valueOf(0x07)).toInt()
val partition = bits.shiftRight(82).and(BigInteger.valueOf(0x07)).toInt()
// далее по partition table
}ספריות מוכנות: LazyColumn או key(item.epc). למידע נוסף, ראו GS1 EPC Tag Data Standard.
השוואה בין RFID וברקודים
| פרמטר | ברקוד | RFID |
|---|---|---|
| מהירות סריקה | פריט אחד/שנייה | 200 תגיות ב-3 שניות |
| דרישת קו ראייה | כן | לא |
| כתיבת נתונים מחדש | לא | כן (חלק מהתגיות) |
| עמידות להפרעות | גבוהה | בינונית (מתכת, נוזל) |
| עלות תגית | $0.01 | $0.05–$0.50 |
RFID מהיר פי 10–100 לסריקה בכמות גדולה אך דורש התקנה ראשונית ובחירת חומרה.
השוואה של שיטות פענוח EPC
| שיטה | ביצועים | תמיכת GS1 | מורכבות אינטגרציה |
|---|---|---|---|
| SGTIN-96 ידני | גבוהים (ילידיים) | מלאה | בינונית |
| ספריית EPCIS | בינוניים (HTTP) | מלאה | נמוכה |
| שירות ענן | נמוכים (REST) | חלקית | מינימלית |
לאפליקציות מובייל, האפשרות הראשונה היא אופטימלית—הכי פחות תלויות ומהירות מקסימלית.
מה כלול בעבודה
- עיצוב ארכיטקטורה של מכונת מצבי המלאי
- פיתוח מודול דדופליקציה באמצעות
@Composable fun InventoryResultsScreen(session: InventorySession) { val matched by session.matchedItems.collectAsState() val missing by session.missingItems.collectAsState() val scanned by session.scannedEpcs.collectAsState() Column { 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) } } } } - יישום אחסון לא מקוון עם Room
- הגדרת סנכרון דרך WorkManager
- אינטגרציה עם קורא BLE (Zebra, מותאם אישית)
- פענוח ואימות GS1 EPC
- ממשק משתמש Jetpack Compose עם התקדמות מונפשת
- חתימת קוד, provisioning, פרסום ל-App Store ו-Google Play
- תיעוד והדרכת מפעילים
העריכו כיצד מלאי RFID יכול לצמצם את זמן המחסן שלכם—קבלו ייעוץ מהמהנדסים שלנו.
תהליך
- אנליטיקה—חקר תהליכי מחסן, סוגי תגיות, WMS.
- אב-טיפוס—MVP תוך 3–5 ימים עם פונקציונליות בסיסית.
- פיתוח—ספינטים איטרטיביים של שבועיים.
- QA—בדיקות עומס עם קוראים אמיתיים.
- פריסה—פרסום לחנויות ומסירה.
לוח זמנים
אפליקציית מלאי מובייל עם קורא BLE של Zebra/מותאם אישית, Room לא מקוון, פענוח GS1 וסנכרון: מ-5 ימים (מחסן פשוט, קורא אחד, סוג תגית אחד) עד 2–3 שבועות (מיקומים מרובים, סוגי תגיות מרובים, סכמת EPC מותאמת אישית, אינטגרציית REST עם WMS).
צרו קשר להערכת פרויקט—נשיב תוך יום. בקשו ייעוץ, ונראה לכם כיצד ליישם מלאי RFID במחסן שלכם. אנו מבטיחים עמידה בהנחיות App Store Review ו-Google Play, כמו גם תמיכה מלאה בכל השלבים.







