DEV Community

Alkan
Alkan

Posted on

React Native ile Endüstriyel Barkod Okuma: Ters Renk Data Matrix Kodları ve Zebra DataWedge Entegrasyonu

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"
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Kontrast Algılama: Çoğu barkod okuyucu, karanlık zemin üzerine açık kod yapısını beklemez
  2. Otomatik Eşik Değeri: Görüntü işleme algoritmaları, ters renk için optimize edilmemiştir
  3. 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,
});
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

Üç katmanlı fallback sistemi:

  1. ML Kit → En güvenilir, Google'ın AI tabanlı çözümü
  2. ZXing → Agresif ayarlarla (tryInvert, tryRotate)
  3. 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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
}, []);
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

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}
/>
Enter fullscreen mode Exit fullscreen mode

Akış:

  1. RS507 tetik tuşuna basılır
  2. DataWedge barkodu okur
  3. Keystroke output → TextInput'a yazar
  4. onChangeText tetiklenir
  5. 200ms debounce sonrası otomatik işleme
  6. 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
Enter fullscreen mode Exit fullscreen mode

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
  };
};
Enter fullscreen mode Exit fullscreen mode

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 };
};
Enter fullscreen mode Exit fullscreen mode

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);
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

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'
    });
  }
});
Enter fullscreen mode Exit fullscreen mode

API Response Formatı

Başarılı Yanıt:

{
  "success": true,
  "message": "Successfully saved 25 barcodes",
  "transactionId": "TXN-2025-12-19-001234"
}
Enter fullscreen mode Exit fullscreen mode

Hata Yanıtı:

{
  "success": false,
  "message": "All barcodes must have the same GTIN",
  "errorCode": "INVALID_GTIN_MISMATCH"
}
Enter fullscreen mode Exit fullscreen mode

Güvenlik Notları

Backend API entegrasyonunda dikkat edilmesi gerekenler:

  1. Authentication & Authorization

    • JWT token kullanımı (Bearer token)
    • Token'ların güvenli saklanması (Keychain/KeyStore)
    • Token refresh mekanizması
  2. API Key Management

    • Hardcoded API key'ler kullanmayın
    • Environment variables veya secure storage
    • Cihaz bazlı unique identifier (device ID)
  3. HTTPS Zorunluluğu

    • TLS 1.2+ kullanın
    • Certificate pinning düşünün
    • Man-in-the-middle saldırılarına karşı koruma
  4. Rate Limiting

    • Backend'de rate limiting uygulayın
    • Mobil uygulamada retry logic ekleyin
    • Exponential backoff stratejisi
  5. 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');
};
Enter fullscreen mode Exit fullscreen mode

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();
};
Enter fullscreen mode Exit fullscreen mode

Switch Component:

<Switch
  trackColor={{ false: "#767577", true: "#f9aa33" }}
  thumbColor={invertScan ? "#ffc107" : "#f4f3f4"}
  onValueChange={saveScannerModeSetting}
  value={invertScan}
/>
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

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>
)}
Enter fullscreen mode Exit fullscreen mode

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"
/>
Enter fullscreen mode Exit fullscreen mode

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 } },
]);
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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));
};
Enter fullscreen mode Exit fullscreen mode

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", "═══════════════════════════════════════")
Enter fullscreen mode Exit fullscreen mode

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:

  1. Normal Data Matrix (karton kutu üzerinde)
  2. Ters renk Data Matrix (blister üzerinde)
  3. DPM lazer işaretli (düşük kontrast)
  4. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

2. DataWedge Profili Oluşmuyor

Log kontrolü:

adb logcat | grep DataWedge
Enter fullscreen mode Exit fullscreen mode

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!
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

Sonuç ve Öneriler

Bu projede React Native ile endüstriyel seviye bir barkod okuma sistemi geliştirdik. Başlıca özellikler:

✅ Başarılar

  1. Hibrit Yaklaşım: Kamera + DataWedge + RS507
  2. Ters Renk Desteği: ML Kit ile görüntü ters çevirme
  3. Otomatik Yapılandırma: DWDemo'ya gerek yok
  4. GS1 Uyumlu: Standart compliant parsing
  5. RESTful API: Modern backend entegrasyonu
  6. Kullanıcı Dostu: Dual mode, görsel feedback

📚 Öğrenilenler

  1. DataWedge Intent Yapısı: Explicit intent kritik
  2. Frame Processor Lifecycle: Ref count management
  3. Bitmap İşleme: ColorMatrix ile inversion
  4. Worklets: JS bridge optimization
  5. GS1 Parsing: Group separator management
  6. REST API Design: Batch processing ve error handling

🚀 Gelecek İyileştirmeler

  1. OCR Entegrasyonu: Baskı hataları için fallback
  2. Offline-First Mimari: SQLite cache + background sync queue
  3. Real-time Validation: Backend'den anlık GTIN doğrulama
  4. AI Preprocessing: Görüntü kalitesi iyileştirme (sharpening, denoising)
  5. Multi-Device Support: Honeywell, Datalogic, SOTI vb.
  6. 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

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)