Giriş
Modern lojistik ve depo yönetimi uygulamalarında barkod okuma, operasyonların omurgasını oluşturur. Ancak standart QR kod ve barkod okuma kütüphaneleri, endüstriyel ortamlarda karşılaşılan bazı özel durumlarda yetersiz kalabilir. Özellikle ters renk (inverted) Data Matrix kodları - beyaz zemin üzerine siyah yerine, siyah zemin üzerine beyaz kodlar - mobil kamera tabanlı okuyucular için ciddi bir zorluk teşkil eder.
Bu yazıda, React Native ile geliştirdiğimiz ve Zebra TC15 gibi endüstriyel el terminallerinde çalışan bir barkod okuma sistemi detaylandırılacaktır. Sistem, hem normal barkodları hem de ters renk Data Matrix kodlarını başarıyla okuyabilen hibrit bir yaklaşım sunmaktadır.
Proje Mimarisi
Projemiz üç ana katmandan oluşmaktadır:
1. React Native Katmanı
- Vision Camera: Kamera erişimi ve frame processing
- Dual Scanner System: Normal mod ve gelişmiş mod olmak üzere iki ayrı tarama sistemi
- GS1 Parser: Barkod standardına uygun veri çıkarma
2. Native Android Katmanı
- DataWedge Module (Kotlin): Zebra DataWedge SDK entegrasyonu
- ML Kit Plugin (Java): Google ML Kit tabanlı barkod okuma
- DPM Plugin (Java): Direct Part Marking için özel işlemler
3. Zebra DataWedge Entegrasyonu
- Otomatik profil yapılandırması
- RS507 Bluetooth scanner desteği
- Ters renk (inverse) decode ayarları
Teknoloji Stack'i
{
"react-native": "0.78.0",
"react-native-vision-camera": "^4.7.3",
"@mgcrea/vision-camera-barcode-scanner": "^0.12.3",
"vision-camera-zxing": "^0.1.1",
"react-native-worklets-core": "^1.6.2",
"axios": "^1.13.2"
}
Native Dependencies:
- Google ML Kit Barcode Scanning
- ZXing (Zebra Crossing) Core
- Zebra DataWedge SDK (cihazda yerleşik)
Problem: Ters Renk Data Matrix Kodları
Neden Özel Bir Çözüm Gerekti?
Standart barkod okuma kütüphaneleri (react-native-vision-camera, @mgcrea/vision-camera-barcode-scanner), normal Data Matrix kodlarını kolayca okurken, ters renk kodlarda başarısız oluyorlar. Bunun nedenleri:
- Kontrast Algılama: Çoğu barkod okuyucu, karanlık zemin üzerine açık kod yapısını beklemez
- Otomatik Eşik Değeri: Görüntü işleme algoritmaları, ters renk için optimize edilmemiştir
- DPM (Direct Part Marking): Endüstriyel parçalar üzerine lazer veya nokta vuruş ile işaretlenen kodlar, düşük kontrastlıdır
Gerçek Dünya Senaryosu
İlaç endüstrisinde, karton kutular üzerine basılan normal Data Matrix kodlarının yanı sıra, ilaç blisterlerinin üzerine lazer ile işaretlenmiş ters renk kodlar bulunmaktadır. Bu kodlar:
- Siyah arka plan üzerine beyaz noktalardan oluşur
- Çok küçük boyutludur (5x5mm civarı)
- Düşük kontrastlıdır
- Yansıma ve ışık problemlerine hassastır
Çözüm 1: Dual Scanner Sistemi
Projemizde iki ayrı tarama modu kullanıyoruz:
Normal Mod (Hızlı Tarama)
Normal barkodlar için optimize edilmiş kamera tabanlı tarama:
// Normal barkod tarayıcı - @mgcrea/vision-camera-barcode-scanner
const { props: normalScannerProps } = useBarcodeScanner({
fps: 5,
barcodeTypes: [
'data-matrix', // Öncelik 1
'qr',
'ean-13',
'ean-8',
'code-128',
'code-39',
'code-93',
'itf',
'upc-a',
'upc-e'
],
onBarcodeScanned: handleNormalBarcodeScanned,
});
Avantajları:
- Hızlı ve düşük CPU kullanımı
- Çoklu barkod formatı desteği
- 5 FPS ile dengeli performans
Dezavantajları:
- Ters renk kodları okuyamaz
- DPM kodlarında zayıf performans
Gelişmiş Mod (Ters Renk + DPM)
Zorlu kodlar için özel işlenmiş frame processor:
const invertedFrameProcessor = useFrameProcessor((frame) => {
'worklet';
if (!isActive || !invertScan) return;
try {
let results = [];
let source = 'unknown';
// Öncelik 1: ML Kit Barcode Scanning
if (mlKitPlugin != null) {
const pluginResults = mlKitPlugin.call(frame, {});
if (pluginResults?.length > 0) {
source = 'ml-kit';
results = pluginResults;
}
}
// Öncelik 2: ZXing agresif ayarlar
if (results.length === 0) {
const zxingConfigs = [
{ tryHarder: true, tryInvert: false, formats: ['data-matrix'] },
{ tryHarder: true, tryInvert: true, formats: ['data-matrix'] },
{ tryHarder: true, tryInvert: true, tryRotate: true, formats: ['data-matrix'] },
];
for (const config of zxingConfigs) {
const zxingResults = zxing(frame, config);
if (zxingResults?.length > 0) {
source = 'zxing';
results = zxingResults;
break;
}
}
}
// Öncelik 3: Native DPM plugin
if (results.length === 0 && dpmPlugin != null) {
const pluginResults = dpmPlugin.call(frame, {});
if (pluginResults?.length > 0) {
source = 'native-dpm';
results = pluginResults;
}
}
// Sonuçları işle
results.forEach((res) => {
processInvertedBarcodeJS(res.value, res.bounds);
});
} catch (error) {
console.error('[Advanced Scanner] Error:', error);
}
}, [isActive, invertScan]);
Üç katmanlı fallback sistemi:
- ML Kit → En güvenilir, Google'ın AI tabanlı çözümü
- ZXing → Agresif ayarlarla (tryInvert, tryRotate)
- DPM Plugin → Özel preprocessing ile native çözüm
Çözüm 2: ML Kit ile Görüntü Ters Çevirme
MlKitDataMatrixPlugin.java
Native tarafta, görüntü işleme ile ters renk kodları normal hale getiriyoruz:
private List<Map<String, Object>> callback(Frame frame) {
List<Map<String, Object>> results = new ArrayList<>();
// 1. Normal görüntü ile dene
InputImage normalImage = InputImage.fromMediaImage(image, rotation);
List<Barcode> normalBarcodes = processImage(normalImage, "normal");
if (!normalBarcodes.isEmpty()) {
results.addAll(extractResults(normalBarcodes, "normal"));
} else {
// 2. Ters çevir ve tekrar dene
Bitmap bitmap = yuvToBitmap(yData, width, height);
Bitmap invertedBitmap = invertBitmap(bitmap);
InputImage invertedImage = InputImage.fromBitmap(invertedBitmap, rotation);
List<Barcode> invertedBarcodes = processImage(invertedImage, "inverted");
results.addAll(extractResults(invertedBarcodes, "inverted"));
}
return results;
}
Görüntü ters çevirme:
private Bitmap invertBitmap(Bitmap original) {
Bitmap inverted = Bitmap.createBitmap(
original.getWidth(),
original.getHeight(),
original.getConfig()
);
Canvas canvas = new Canvas(inverted);
Paint paint = new Paint();
// Renk matrisi: Her pikseli ters çevir
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
-1, 0, 0, 0, 255, // R -> 255 - R
0, -1, 0, 0, 255, // G -> 255 - G
0, 0, -1, 0, 255, // B -> 255 - B
0, 0, 0, 1, 0 // A -> A (değişmez)
});
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
canvas.drawBitmap(original, 0, 0, paint);
return inverted;
}
Bu yaklaşım sayesinde:
- Beyaz zemin + siyah kod → Siyah zemin + beyaz kod
- ML Kit bu formatta çok daha başarılı
- %80+ okuma oranı elde ettik
Çözüm 3: Zebra DataWedge Entegrasyonu
DataWedge Nedir?
Zebra el terminallerinde (TC15, TC21, TC52, vb.) yerleşik gelen bir barkod okuma altyapısıdır. Özellikler:
- Donanımsal barkod tarayıcı kontrolü
- RS507 Bluetooth scanner desteği
- Ters renk (inverse) decode yetenekleri
- DPM modu (Direct Part Marking)
- Intent ve Keystroke output
DWDemo Uygulaması Gereksiz!
Zebra cihazlarda, DataWedge profil yapılandırması için genellikle DWDemo adlı yardımcı uygulama kullanılır. Ancak bizim yaklaşımımız:
Otomatik profil oluşturma - DWDemo'ya gerek yok!
// DataWedgeModule.kt
private fun configureDataWedge(enableInverseMode: Boolean = true) {
val profileName = "MyAppProfile" // Uygulamanıza özel bir isim verin
// 1. Profil oluştur - EXPLICIT INTENT
val createIntent = Intent().apply {
action = DATAWEDGE_SEND_ACTION
setPackage("com.symbol.datawedge") // Sadece DataWedge alsın
putExtra("com.symbol.datawedge.api.CREATE_PROFILE", profileName)
}
reactApplicationContext.sendBroadcast(createIntent)
// 2. Profil yapılandırması
val profileConfig = Bundle().apply {
putString("PROFILE_NAME", profileName)
putString("PROFILE_ENABLED", "true")
// App Association
val appList = ArrayList<Bundle>()
val appConfig = Bundle().apply {
putString("PACKAGE_NAME", reactApplicationContext.packageName)
putStringArray("ACTIVITY_LIST", arrayOf("*"))
}
appList.add(appConfig)
putParcelableArrayList("APP_LIST", appList)
// Plugin yapılandırmaları
val pluginConfigs = ArrayList<Bundle>()
// Intent Output
pluginConfigs.add(createIntentPlugin())
// Keystroke Output
pluginConfigs.add(createKeystrokePlugin())
// Barcode Input - TERS RENK DESTEĞİ
pluginConfigs.add(createBarcodePlugin(enableInverseMode))
putParcelableArrayList("PLUGIN_CONFIG", pluginConfigs)
}
// Yapılandırmayı gönder
val configIntent = Intent().apply {
action = DATAWEDGE_SEND_ACTION
setPackage("com.symbol.datawedge")
putExtra("com.symbol.datawedge.api.SET_CONFIG", profileConfig)
}
reactApplicationContext.sendBroadcast(configIntent)
}
Ters Renk Data Matrix Ayarları
DataWedge'de ters renk decode için kritik ayarlar:
private fun createBarcodePlugin(enableInverse: Boolean): Bundle {
return Bundle().apply {
putString("PLUGIN_NAME", "BARCODE")
val barcodeProps = Bundle().apply {
putString("scanner_input_enabled", "true")
// DATA MATRIX TERS RENK
putString("decoder_datamatrix", "true")
// 0 = Normal, 1 = Inverse Only, 2 = Inverse Auto Detect
putString("decoder_datamatrix_inverse", if (enableInverse) "2" else "0")
// DPM (Direct Part Marking) modu - kritik!
putString("dpm_mode", if (enableInverse) "1" else "0")
// QR CODE TERS RENK
putString("decoder_qrcode", "true")
putString("decoder_qr_inverse", if (enableInverse) "2" else "0")
// GELİŞMİŞ AYARLAR
if (enableInverse) {
putString("inverse_1d_mode", "2") // Auto
putString("poor_quality_bcdecode_effort_level", "1")
}
// RS507 BLUETOOTH SCANNER
putString("scanner_selection", "auto")
putString("bt_scanner_enabled", "true")
putString("bt_reconnect", "true")
}
putBundle("PARAM_LIST", barcodeProps)
}
}
Kritik Parametreler:
-
decoder_datamatrix_inverse: "2"→ Otomatik inverse detection -
dpm_mode: "1"→ Direct Part Marking aktif -
poor_quality_bcdecode_effort_level: "1"→ Daha agresif decode
Otomatik Başlatma (App.tsx)
Uygulama ilk açıldığında, DataWedge profili otomatik oluşturuluyor:
// App.tsx
useEffect(() => {
const initializeDataWedge = async () => {
if (Platform.OS !== 'android' || !NativeModules?.DataWedgeModule) {
return;
}
const dwInitialized = await AsyncStorage.getItem('dataWedgeInitialized');
if (!dwInitialized) {
console.log('[App Init] 🚀 FIRST LAUNCH - Initializing DataWedge');
console.log('[App Init] NO NEED FOR DWDemo - Full auto setup!');
await NativeModules.DataWedgeModule.enableDataWedge();
await NativeModules.DataWedgeModule.initializeDataWedge();
await AsyncStorage.setItem('dataWedgeInitialized', 'true');
console.log('[App Init] ✅ DataWedge profile created: MyAppProfile');
console.log('[App Init] 💡 DWDemo is NOT needed!');
}
};
setTimeout(initializeDataWedge, 2000);
}, []);
Sonuç:
- Kullanıcı uygulamayı ilk açtığında profil otomatik oluşturuluyor
- DWDemo'ya hiç ihtiyaç yok
- Zebra cihazlarda %100 otomatik kurulum
Not: MyAppProfile yerine uygulamanıza uygun benzersiz bir profil adı kullanın. Package name'inizle eşleştirmek iyi bir pratiktir (örn: com.yourcompany.yourapp).
Çözüm 4: RS507 Bluetooth Scanner Entegrasyonu
RS507 Nedir?
Zebra RS507, el terminaline bluetooth ile bağlanan yüzük tipi tarayıcıdır:
- Operatörlerin ellerini serbest bırakır
- Tetik tuşu ile hızlı okuma
- TC15 gibi cihazlarla sorunsuz çalışır
Otomatik Bağlanma
Gelişmiş mod açıldığında RS507 otomatik bağlanıyor:
// QrScannerModalNewErrorHand.js
useEffect(() => {
if (invertScan) {
if (Platform.OS === 'android' && NativeModules?.DataWedgeModule) {
console.log('[RS507] 🔗 Attempting to connect Bluetooth Scanner...');
NativeModules.DataWedgeModule.enableDataWedge()
.then(() => {
return NativeModules.DataWedgeModule.connectBluetoothScanner();
})
.then(() => {
console.log('[RS507] ✅ RS507 connection command sent');
console.log('[RS507] 📱 If paired, RS507 should connect automatically');
})
.catch((error) => {
console.log('[RS507] ⚠️ RS507 connection info:', error);
console.log('[RS507] 💡 Normal if not paired yet');
});
}
}
}, [invertScan]);
Keystroke Injection ile Otomatik İşleme
RS507 tetik tuşu ile okunan barkodlar, TextInput'a otomatik yazılıyor:
<TextInput
ref={barcodeInputRef}
style={styles.topBarcodeInput}
value={barcodeInput}
onChangeText={(text) => {
setBarcodeInput(text);
// Barkod geldiğinde otomatik işle
if (text.trim().length > 10) {
// Debounce timeout
if (processingTimeoutRef.current) {
clearTimeout(processingTimeoutRef.current);
}
processingTimeoutRef.current = setTimeout(() => {
const currentValue = text.trim();
// Duplicate check
if (currentValue && currentValue !== lastProcessedBarcodeRef.current) {
lastProcessedBarcodeRef.current = currentValue;
processBarcodeResultRef.current?.(currentValue);
// Input'u temizle
setBarcodeInput('');
// 500ms sonra yeni barkod için hazır
setTimeout(() => {
lastProcessedBarcodeRef.current = '';
barcodeInputRef.current?.focus();
}, 500);
}
}, 200); // 200ms debounce
}
}}
placeholder="Tetik tuşuna basın..."
showSoftInputOnFocus={false}
/>
Akış:
- RS507 tetik tuşuna basılır
- DataWedge barkodu okur
- Keystroke output → TextInput'a yazar
-
onChangeTexttetiklenir - 200ms debounce sonrası otomatik işleme
- Input temizlenir, yeni barkod için hazır
Avantajlar:
- Klavye görünmez (
showSoftInputOnFocus={false}) - Otomatik işleme - Enter'a gerek yok
- Duplicate önleme mekanizması
- Hızlı ardışık okuma
GS1 Standardı ve Barkod Parsing
GS1 Data Matrix Yapısı
İlaç ve lojistik sektöründe kullanılan Data Matrix kodları GS1 standardına uyar:
]d201234567890123421ABC12345678910LOTNO01117250131210630
AI (Application Identifier) Yapısı:
-
01→ GTIN (Global Trade Item Number) - 14 digit -
21→ Serial Number - değişken uzunlukta -
17→ Expiry Date (YYMMDD) - 6 digit -
10→ Lot Number - değişken uzunlukta -
11→ Production Date (YYMMDD) - 6 digit
Group Separator: ASCII 29 (GS) karakteri değişken uzunluk AI'ları sonlandırır
Parser İmplementasyonu
const parseBarcode = (rawValue) => {
// Symbology prefix temizle (]C1, ]D1, vb.)
const normalized = cleanBarcodeValue(rawValue);
const value = normalized.toUpperCase();
if (!value.startsWith("01") || value.length < 16) {
throw new Error("Invalid QR Code format (GTIN missing)");
}
const parsed = {
"01": value.substring(2, 16), // GTIN
};
let cursor = 16;
while (cursor < value.length - 1) {
cursor = skipGroupSeparators(value, cursor);
if (cursor >= value.length - 1) break;
const ai = value.substring(cursor, cursor + 2);
if (!GS1_KNOWN_AIS.includes(ai)) break;
cursor += 2;
cursor = skipGroupSeparators(value, cursor);
// Fixed length AI
if (GS1_FIXED_LENGTH_AIS[ai]) {
const len = GS1_FIXED_LENGTH_AIS[ai];
parsed[ai] = value.substring(cursor, cursor + len);
cursor += len;
continue;
}
// Variable length AI
const maxLen = GS1_VARIABLE_LENGTH_AIS[ai] || 30;
const { field, nextIndex } = extractVariableField(value, cursor, maxLen);
parsed[ai] = field;
cursor = nextIndex;
}
// GTIN'den sıfırları temizle (EAN-11)
const ean11 = parsed["01"].replace(/^0+/, "");
return {
Ean11: ean11,
Sernr: parsed["21"] || parsed["10"], // Serial
Vfdat: normalizeGs1Date(parsed["17"]), // Expiry
Lotnr: parsed["10"] || parsed["21"], // Lot
Mfdat: normalizeGs1Date(parsed["11"]), // Production
};
};
Group Separator Yönetimi
Değişken uzunluklu AI'lar GS (ASCII 29) ile sonlandırılır:
const GS1_GROUP_SEPARATOR = String.fromCharCode(29);
const skipGroupSeparators = (value, startIndex = 0) => {
let cursor = startIndex;
while (cursor < value.length && value[cursor] === GS1_GROUP_SEPARATOR) {
cursor += 1;
}
return cursor;
};
const extractVariableField = (value, start, maxLength = 30) => {
let cursor = skipGroupSeparators(value, start);
let end = Math.min(value.length, cursor + maxLength);
// GS bulursa orada sonlandır
const gsIndex = value.indexOf(GS1_GROUP_SEPARATOR, cursor);
if (gsIndex !== -1 && gsIndex < end) {
end = gsIndex;
}
// Fixed AI bulursa orada sonlandır
const fixedIndex = findNextFixedAiIndex(value, cursor);
if (fixedIndex !== -1 && fixedIndex < end) {
end = fixedIndex;
}
const field = value.substring(cursor, end);
let nextIndex = end;
if (gsIndex !== -1 && gsIndex === end) {
nextIndex = gsIndex + 1; // GS'i atla
}
return { field, nextIndex };
};
Backend API Entegrasyonu
Okunan barkodlar, modern RESTful API üzerinden backend sistemine gönderilir. Bu sayede:
- Merkezi veri yönetimi sağlanır
- Gerçek zamanlı stok takibi yapılır
- Audit trail (denetim izi) oluşturulur
- Çoklu cihaz senkronizasyonu mümkün olur
Frontend Entegrasyon Kodu
Okunan barkodlar parse edildikten sonra batch olarak API'ye gönderilir:
const sendBarcodesToAPI = async (barcodes, gtin) => {
// Personel bilgisini AsyncStorage'dan al
const employeeId = await AsyncStorage.getItem('employeeId');
// Barkodları parse et
const items = barcodes.map(barcode => {
const parsed = parseBarcode(barcode);
return {
gtin: parsed.Ean11,
serialNumber: parsed.Sernr,
expiryDate: parsed.Vfdat,
lotNumber: parsed.Lotnr,
productionDate: parsed.Mfdat
};
});
// REST API request payload
const payload = {
employeeId: employeeId,
gtin: gtin,
scannedAt: new Date().toISOString(),
items: items,
deviceInfo: {
platform: Platform.OS,
version: Platform.Version
}
};
try {
const response = await axios.post(
`${API_BASE_URL}/api/barcodes/batch`,
payload,
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_TOKEN}`,
'X-Device-Id': await AsyncStorage.getItem('deviceId'),
},
timeout: 30000, // 30 saniye timeout
}
);
// Backend'den başarılı yanıt kontrolü
if (response.status === 200 || response.status === 201) {
if (response.data.success) {
return {
success: true,
message: response.data.message,
transactionId: response.data.transactionId
};
}
}
throw new Error(response.data.message || 'Unknown error');
} catch (error) {
// Hata detaylarını logla
console.error('[API] Error sending barcodes:', error);
if (error.response) {
// Backend'den hata yanıtı geldi
throw new Error(
error.response.data.message ||
`Server error: ${error.response.status}`
);
} else if (error.request) {
// Request gönderildi ama yanıt gelmedi
throw new Error('No response from server. Check your connection.');
} else {
// Request oluşturulurken hata
throw new Error(error.message);
}
}
};
Backend API Endpoint Örneği
Backend tarafında beklenen endpoint yapısı (Node.js/Express örneği):
// POST /api/barcodes/batch
app.post('/api/barcodes/batch', authenticate, async (req, res) => {
try {
const { employeeId, gtin, items, scannedAt, deviceInfo } = req.body;
// Validasyon
if (!employeeId || !gtin || !items || items.length === 0) {
return res.status(400).json({
success: false,
message: 'Missing required fields'
});
}
// Tüm barkodların aynı GTIN'e sahip olduğunu kontrol et
const allSameGtin = items.every(item => item.gtin === gtin);
if (!allSameGtin) {
return res.status(400).json({
success: false,
message: 'All barcodes must have the same GTIN'
});
}
// Database'e kaydet
const transaction = await db.transaction(async (trx) => {
const batchId = await trx('barcode_batches').insert({
employee_id: employeeId,
gtin: gtin,
scanned_at: scannedAt,
device_platform: deviceInfo.platform,
item_count: items.length
}).returning('id');
// Her bir barkodu kaydet
const barcodeRecords = items.map(item => ({
batch_id: batchId[0],
gtin: item.gtin,
serial_number: item.serialNumber,
expiry_date: item.expiryDate,
lot_number: item.lotNumber,
production_date: item.productionDate
}));
await trx('barcodes').insert(barcodeRecords);
return batchId[0];
});
res.status(201).json({
success: true,
message: `Successfully saved ${items.length} barcodes`,
transactionId: transaction
});
} catch (error) {
console.error('Batch save error:', error);
res.status(500).json({
success: false,
message: 'Failed to save barcodes'
});
}
});
API Response Formatı
Başarılı Yanıt:
{
"success": true,
"message": "Successfully saved 25 barcodes",
"transactionId": "TXN-2025-12-19-001234"
}
Hata Yanıtı:
{
"success": false,
"message": "All barcodes must have the same GTIN",
"errorCode": "INVALID_GTIN_MISMATCH"
}
Güvenlik Notları
Backend API entegrasyonunda dikkat edilmesi gerekenler:
-
Authentication & Authorization
- JWT token kullanımı (Bearer token)
- Token'ların güvenli saklanması (Keychain/KeyStore)
- Token refresh mekanizması
-
API Key Management
- Hardcoded API key'ler kullanmayın
- Environment variables veya secure storage
- Cihaz bazlı unique identifier (device ID)
-
HTTPS Zorunluluğu
- TLS 1.2+ kullanın
- Certificate pinning düşünün
- Man-in-the-middle saldırılarına karşı koruma
-
Rate Limiting
- Backend'de rate limiting uygulayın
- Mobil uygulamada retry logic ekleyin
- Exponential backoff stratejisi
-
Data Validation
- Backend'de tüm input'ları validate edin
- GTIN formatı doğrulama
- Tarih formatı kontrolleri (expiry date, production date)
// Token güvenli saklama örneği
import * as SecureStore from 'expo-secure-store';
const saveAuthToken = async (token) => {
await SecureStore.setItemAsync('api_token', token);
};
const getAuthToken = async () => {
return await SecureStore.getItemAsync('api_token');
};
Kullanıcı Deneyimi Optimizasyonları
1. Dual Mode Switch
Kullanıcı, tarama modunu anında değiştirebilir:
const saveScannerModeSetting = async (value) => {
await AsyncStorage.setItem('invertScan', JSON.stringify(value));
setInvertScan(value);
// DataWedge'e inverse mode değişikliğini bildir
if (Platform.OS === 'android' && NativeModules?.DataWedgeModule) {
await NativeModules.DataWedgeModule.setInverseMode(value);
}
// Tarama durumunu sıfırla
setScannedCount(0);
scannedBarcodesRef.current = new Set();
};
Switch Component:
<Switch
trackColor={{ false: "#767577", true: "#f9aa33" }}
thumbColor={invertScan ? "#ffc107" : "#f4f3f4"}
onValueChange={saveScannerModeSetting}
value={invertScan}
/>
2. Görsel Geri Bildirim
Barkod okununca merkezi vurgu kutusu:
if (!scannedBarcodesRef.current.has(normalizedBarcodeValue)) {
scannedBarcodesRef.current.add(normalizedBarcodeValue);
setScannedCount((prevCount) => prevCount + 1);
// Yeşil vurgu - yeni barkod
highlightRef.current = { bounds: finalBounds, color: 'green' };
} else {
// Kırmızı vurgu - duplicate
highlightRef.current = { bounds: finalBounds, color: 'red' };
}
setTimeout(() => {
highlightRef.current = null;
}, 500);
3. Otomatik Gönderim
Hedef adet girilirse, otomatik backend API'ye gönderim:
useEffect(() => {
if (autoSend &&
targetCount &&
parseInt(targetCount) === scannedCount &&
scannedCount > 0 &&
!autoSendTriggered) {
setAutoSendTriggered(true);
setIsActive(false);
setTimeout(() => {
handleSendBarcodes();
}, 500);
}
}, [targetCount, scannedCount, autoSend]);
4. Gelişmiş Mod Rehber Kutusu
Gelişmiş modda, kullanıcıya sürekli görsel rehber:
{invertScan && (
<View style={styles.scanGuideBox}>
{/* Kesik çizgili gri çerçeve */}
<View style={styles.scanGuideLineTop} />
<View style={styles.scanGuideLineBottom} />
<View style={styles.scanGuideLineLeft} />
<View style={styles.scanGuideLineRight} />
{/* Köşe işaretleri */}
<View style={[styles.scanGuideCorner, styles.scanGuideCornerTopLeft]} />
<View style={[styles.scanGuideCorner, styles.scanGuideCornerTopRight]} />
<View style={[styles.scanGuideCorner, styles.scanGuideCornerBottomLeft]} />
<View style={[styles.scanGuideCorner, styles.scanGuideCornerBottomRight]} />
</View>
)}
Performans İyileştirmeleri
1. Frame Processor FPS Optimizasyonu
// Normal mod - 5 FPS (yeterli hız)
<Camera
frameProcessorFps={5}
preset="high"
{...normalScannerProps}
/>
// Gelişmiş mod - 2 FPS (CPU tasarrufu, kaliteli işleme)
<Camera
frameProcessor={invertedFrameProcessor}
frameProcessorFps={2}
preset="max"
videoStabilizationMode="cinematic-extended"
/>
Neden düşük FPS?
- Data Matrix decode CPU intensive
- 2 FPS bile saniyede 2 okuma fırsatı
- Gerçek dünya testlerinde yeterli
2. Yüksek Çözünürlük Formatı
const highResFormat = useCameraFormat(device, [
{ videoResolution: { width: 1920, height: 1080 } },
{ videoResolution: { width: 1280, height: 720 } },
]);
Neden yüksek çözünürlük?
- Küçük Data Matrix kodları için detay kritik
- ML Kit ve ZXing daha başarılı
- 1080p ideal, 720p minimum
3. Worklets ile JS Bridge Optimizasyonu
Frame processor'dan JS'e veri aktarımı:
const processInvertedBarcodeJS = Worklets.createRunOnJS((barcodeValue, bounds) => {
processBarcodeResult(barcodeValue, bounds);
});
// Worklet içinde
if (barcodeDetected) {
processInvertedBarcodeJS(value, bounds);
}
Avantaj: UI thread bloklanmaz
Hata Yönetimi ve Logging
Detaylı Log Sistemi
const logError = async (error, context, additionalData = {}) => {
const errorLog = {
timestamp: new Date().toISOString(),
error: {
message: error.message,
stack: error.stack,
name: error.name
},
context: context,
additionalData: additionalData,
deviceInfo: {
platform: Platform.OS,
version: Platform.Version
}
};
console.error(`[QR Scanner Error - ${context}]:`, errorLog);
// AsyncStorage'a kaydet
const existingLogs = await AsyncStorage.getItem('crashLogs');
const logs = existingLogs ? JSON.parse(existingLogs) : [];
logs.unshift(errorLog);
// Son 50 hatayı sakla
const limitedLogs = logs.slice(0, 50);
await AsyncStorage.setItem('crashLogs', JSON.stringify(limitedLogs));
};
Native Tarafta Detaylı Loglar
android.util.Log.d("DataWedge", "═══════════════════════════════════════")
android.util.Log.d("DataWedge", "📡 BROADCAST RECEIVED")
android.util.Log.d("DataWedge", "Data String: $data")
android.util.Log.d("DataWedge", "Label Type: $type")
android.util.Log.d("DataWedge", "═══════════════════════════════════════")
Emoji kullanımı ile log filtreleme kolaylaşır:
-
📡→ DataWedge broadcast -
✅→ Başarılı işlem -
❌→ Hata -
⚠️→ Uyarı -
🔄→ Mod değişikliği
Test ve Sonuçlar
Gerçek Dünya Test Senaryosu
Ortam: İlaç deposu, karışık aydınlatma
Test Edilen Barkodlar:
- Normal Data Matrix (karton kutu üzerinde)
- Ters renk Data Matrix (blister üzerinde)
- DPM lazer işaretli (düşük kontrast)
- QR kod (karma)
Sonuçlar
| Mod | Normal DM | Ters Renk DM | DPM | QR |
|---|---|---|---|---|
| Normal Mod | 95% | 10% | 5% | 98% |
| Gelişmiş Mod (Kamera) | 90% | 75% | 60% | 95% |
| Gelişmiş Mod (RS507) | 98% | 85% | 80% | 99% |
Önemli Bulgular:
- ML Kit ters renk için en başarılı
- RS507 ile DPM okuma %80+
- Dual system ile tüm senaryolar kapsandı
Performans Metrikleri
Normal Mod:
- CPU: %15-20
- Pil tüketimi: Düşük
- Ortalama okuma süresi: 0.5s
Gelişmiş Mod (Kamera):
- CPU: %35-45
- Pil tüketimi: Orta
- Ortalama okuma süresi: 1.5s
Gelişmiş Mod (RS507):
- CPU: %10-15 (native)
- Pil tüketimi: Düşük
- Ortalama okuma süresi: 0.3s
Sorun Giderme
1. RS507 Bağlanmıyor
Çözüm:
# Zebra cihazda Bluetooth ayarları
Settings → Bluetooth → Scan for devices
# RS507'yi bul ve pair et
# Uygulama otomatik bağlanacak
2. DataWedge Profili Oluşmuyor
Log kontrolü:
adb logcat | grep DataWedge
Sorun: Explicit intent kullanılmamış
Çözüm:
// YANLIŞ
val intent = Intent("com.symbol.datawedge.api.ACTION")
// DOĞRU
val intent = Intent().apply {
action = "com.symbol.datawedge.api.ACTION"
setPackage("com.symbol.datawedge") // Kritik!
}
3. Ters Renk Kodlar Hala Okunmuyor
Kontrol listesi:
- [ ]
decoder_datamatrix_inverse: "2"ayarlandı mı? - [ ]
dpm_mode: "1"aktif mi? - [ ] Kamera formatı yüksek çözünürlükte mi? (min 720p)
- [ ] Işık yeterli mi? (çok parlak veya karanlık olmamalı)
- [ ] Kod zarar görmemiş mi?
4. ML Kit Plugin Çalışmıyor
Frame reference hatasını çözmek:
// Lifecycle management
frame.incrementRefCount(); // Frame'i uzat
try {
// İşlemler
Image image = frame.getImage();
// ...
image.close(); // Önemli: close et
} finally {
// frame.decrementRefCount() - Otomatik (VisionCamera 4.x)
}
Sonuç ve Öneriler
Bu projede React Native ile endüstriyel seviye bir barkod okuma sistemi geliştirdik. Başlıca özellikler:
✅ Başarılar
- Hibrit Yaklaşım: Kamera + DataWedge + RS507
- Ters Renk Desteği: ML Kit ile görüntü ters çevirme
- Otomatik Yapılandırma: DWDemo'ya gerek yok
- GS1 Uyumlu: Standart compliant parsing
- RESTful API: Modern backend entegrasyonu
- Kullanıcı Dostu: Dual mode, görsel feedback
📚 Öğrenilenler
- DataWedge Intent Yapısı: Explicit intent kritik
- Frame Processor Lifecycle: Ref count management
- Bitmap İşleme: ColorMatrix ile inversion
- Worklets: JS bridge optimization
- GS1 Parsing: Group separator management
- REST API Design: Batch processing ve error handling
🚀 Gelecek İyileştirmeler
- OCR Entegrasyonu: Baskı hataları için fallback
- Offline-First Mimari: SQLite cache + background sync queue
- Real-time Validation: Backend'den anlık GTIN doğrulama
- AI Preprocessing: Görüntü kalitesi iyileştirme (sharpening, denoising)
- Multi-Device Support: Honeywell, Datalogic, SOTI vb.
- Analytics Dashboard: Okuma başarı oranları ve performans metrikleri
💡 Öneriler
Zebra cihaz kullananlar için:
- DataWedge SDK'yı ihmal etmeyin
- Profile auto-configuration yazın
- RS507 entegrasyonu kullanıcı deneyimini çok iyileştirir
Ters renk kod okumak isteyenler için:
- ML Kit'e öncelik verin
- Bitmap inversion stratejisi çalışıyor
- Yüksek çözünürlük kritik (min 720p)
React Native geliştiriciler için:
- Frame processor'lar güçlü ama dikkatli kullanın
- Worklets ile JS bridge optimize edin
- Native module yazmaktan korkmayın
Backend entegrasyonu için:
- Batch processing endpoint'leri kullanın (tek tek gönderim yerine)
- Transaction ID ile işlem takibi yapın
- Offline-first yaklaşım düşünün (SQLite + sync)
- Proper error handling ve retry mekanizmaları ekleyin
Kaynaklar ve Linkler
Resmi Dokümantasyon
- Zebra DataWedge API
- React Native Vision Camera
- Google ML Kit Barcode Scanning
- GS1 General Specifications
- REST API Design Best Practices
- JWT Authentication
Açık Kaynak Kütüphaneler
Topluluk
Yazar Notu: Bu proje, gerçek dünya lojistik operasyonlarında kullanılmakta ve günde ortalama 5000+ barkod okuma yapılmaktadır. Herhangi bir sorunuz veya geri bildiriminiz varsa, yorumlarda paylaşabilirsiniz.
GitHub: [Projeyi yıldızlamayı unutmayın! ⭐]
Etiketler: #ReactNative #DataWedge #Zebra #BarcodeScanning #DataMatrix #MLKit #ZXing #MobileDevelopment #EnterpriseApps #GS1 #RESTful #BackendAPI
Son güncelleme: Aralık 2025
Top comments (0)