RFID Exact Product Locator Development
Budget: $250 – $750 USD
# Developer Spec — Exact-Product RFID Filter (58-bit Select)
### Battle Sports · Zebra MC3330xR · UHF RAIN RFID
## 1. Objective
Build a small Android app for the **Zebra MC3330xR** that **inventories and locates a single exact product**, distinguishing products whose tag data differs by as little as **one bit**. Operator enters/scans a **UPC**; the app applies a precise **58-bit Gen2 Select pre-filter** so the reader responds to **only that product**, with a proximity readout to walk to it.
This does **not** exist in Zebra's free *123RFID Mobile* app, whose pre-filter UI cannot reliably apply a sub-nibble (58-bit) mask. The Zebra RFID SDK exposes the bit-level Select parameters directly — hence a custom app.
## 2. Why 58 bits
Tags are **GS1 SGTIN-96** (96-bit EPC): Header 8 (`0x30`) · Filter 3 (`1`) · Partition 3 · Company Prefix 20–40 · Item Reference 24–4 (prefix+itemref always = 44) · **Serial 38 (unique per unit)**.
The **product identity (GTIN) is the first 58 bits** (96−38). The last 38 bits are a per-unit serial. To match "this product, any serial," Select on **exactly the first 58 bits**. Hex prefixes only express whole nibbles:
- **56-bit (14 hex)** → too short; misses the distinguishing bit on near-twins.
- **60-bit (15 hex)** → reaches into the per-unit serial; matches only *some* units.
Only a **non-nibble-aligned 58-bit** mask works, and only the SDK can set it. That is the entire reason for this project.
## 3. Platform
- **Device:** Zebra MC3330xR (integrated UHF RFID, model MC333U…).
- **OS:** Android 11 (Zebra GMS build).
- **SDK:** **Zebra RFID SDK for Android**, package `com.zebra.rfid.api3` ("RFID API3"). Use the current release; **verify method signatures against that version** — the API drifts across releases.
- **Reader:** internal reader; enumerate via `Readers(context, ENUM_TRANSPORT.SERVICE_USB)` and connect the first available.
- **Build:** Android Studio, signed APK; deploy via sideload/StageNow/MDM.
## 4. Core requirement — the 58-bit pre-filter (the crux)
Apply a Gen2 Select before Inventory/Locate. Reference (verify vs your SDK version):
```java
import com.zebra.rfid.api3.*;
PreFilters preFilters = new PreFilters();
PreFilters.PreFilter pf = preFilters.new PreFilter();
pf.setMemoryBank(MEMORY_BANK.MEMORY_BANK_EPC);
pf.setBitOffset(32); // *** skip StoredCRC(16)+StoredPC(16); EPC starts at bit 32
pf.setTagPattern("30343183AC045D0"); // 58-bit pattern packed into nibbles (see note)
pf.setTagPatternBitCount(58); // *** THE KEY: compare exactly 58 bits, not 56 or 60
pf.setFilterAction(FILTER_ACTION.FILTER_ACTION_ASSERT_UNASSERT_SL); // assert SL on match, deassert others
pf.setFilterTarget(TARGET.SESSION_S1); // hold state across rounds (verify enum name)
reader.Actions.PreFilters.add(new PreFilters.PreFilter[]{ pf });
```
Then inventory querying **only SL-asserted tags** so non-matches are silenced:
```java
Antennas.SingulationControl s = reader.Config.Antennas.getSingulationControl(1);
// configure state-aware (SL) singulation consistent with FILTER_ACTION above
reader.Config.Antennas.setSingulationControl(1, s);
reader.Actions.Inventory.perform(); // only the exact product returns
```
**Pattern packing:** `tagPattern` is nibble/byte-packed; `tagPatternBitCount = 58` tells the radio to compare only the first 58 bits (the last 2 bits of the 15th nibble are ignored padding). Confirm whether your build wants a hex `String` or `byte[]`.
**Critical:** `setBitOffset(32)` and `setTagPatternBitCount(58)` make or break this. Wrong offset → mask lands on CRC/PC junk. bitCount 56 → near-twins collide.
## 5. UPC → 58-bit pattern (validated logic — reuse verbatim)
```
encode(UPC12, companyPrefixLen):
gtin14 = leftPad(UPC12, 14, '0')
indicator= gtin14[0]; middle12 = gtin14[1..12]
companyPrefix = middle12[0 .. companyPrefixLen-1]
itemRefGtin = middle12[companyPrefixLen .. end]
itemField = indicator + itemRefGtin // decimal concat
partition = {6->6,7->5,8->4,9->3,10->2,11->1,12->0}[companyPrefixLen]
cpBits = {6->20,7->24,8->27,9->30,10->34,11->37,12->40}[companyPrefixLen]
irBits = {6->24,7->20,8->17,9->14,10->10,11->7,12->4}[companyPrefixLen]
bits = bin(0x30,8) + bin(1,3) + bin(partition,3)
+ bin(int(companyPrefix),cpBits) + bin(int(itemField),irBits)
return first 58 bits of bits // Select pattern; bitCount=58, offset=32
```
### WARNING — Company-prefix-length ambiguity (must handle — real on this data)
A UPC does not carry its GS1 company-prefix length, and **this supplier encodes inconsistently** — the same brand uses **7-digit (partition 5)** on some products, **8-digit (partition 4)** on others. A pure UPC->pattern compute will sometimes pick the wrong partition and match nothing. Resolve, in priority order:
1. **Seed from a reference tag (recommended).** On first encounter, operator scans **one physical unit's RFID tag**; app decodes its EPC (reverse of §5), extracts the **actual partition + first-58-bit pattern**, stores it keyed by UPC. All later locates use the stored pattern — ground truth, ambiguity gone.
2. **GS1 GCP Length Table** lookup (access restricted since Jan 2026; budget to license/cache a copy).
3. **Compute both 7- and 8-digit candidates, try in order** (fallback).
Include an EPC-hex -> UPC+partition decoder for step 1 (also validated in scoping).
## 6. App features
1. **Product entry:** type a UPC, or scan the printed UPC barcode via the built-in imager (DataWedge -> field).
2. **Resolve pattern** per §5; if unknown, prompt to seed from a reference-tag scan.
3. **Apply pre-filter** (§4).
4. **Inventory mode:** live **count** of the exact product (near-twins excluded).
5. **Locate mode:** proximity (RSSI/%) + beeper intensifying with proximity, trigger-driven.
6. **Power control:** expose TX power (0–30 dBm) for close-range pinpointing.
7. **Session/target** handling so the filter persists across rounds.
## 7. Data model (minimum)
`products`: `upc` (PK), `gtin14`, `company_prefix_len`, `partition`, `pattern_hex` (58-bit packed), `pattern_bitcount` (=58), `bit_offset` (=32), `source` (`reference_tag`|`gcp_table`|`computed`), `verified_at`.
## 8. Acceptance test (pass/fail — use these exact values)
Two **real, different** products differing by one bit. Pass only if each is isolated.
| UPC | Encoding | 58-bit pattern (hex, packed) | bitCount | bitOffset |
|---|---|---|---|---|
| **811243044689** | 7-digit / partition 5 | `30343183AC045D0` | 58 | 32 |
| **811243044702** | 7-digit / partition 5 | `30343183AC045D8` | 58 | 32 |
Both share the **first 56 bits** (`30343183AC045D`) — they collide at 14 hex chars and first differ at **bit index 56**.
- **T1 (isolation):** with both present, load 689 -> report **only 689**, reject all 702. Repeat for 702.
- **T2 (completeness):** with N units of 689 (varying serials) -> return **all N** (matches product, not one serial).
- **T3 (negative control):** set `bitCount = 56` -> both appear (demonstrates the bug the fix resolves).
- **T4 (mixed encoding):** include one 8-digit/partition-4 product -> reference-tag seeding (§5.1) resolves it.
## 9. Deliverables
1. Signed, installable **APK** for Android 11 on the MC3330xR.
2. **Source** (Android Studio project) + build instructions.
3. **UPC->pattern encoder** and **EPC->UPC decoder** as tested modules (unit tests using §8 vectors).
4. Short **operator guide** + deployment notes.
5. Demonstrated §8 tests passing on the physical device.
## 10. Skills / effort
Required: **Android (Java/Kotlin)** + hands-on **Zebra RFID SDK for Android** experience (GS1/EPC a plus; encoding is fully specified). Scope is a focused single-purpose app (one reader, one filter type, two modes) — **days, not weeks** for an experienced dev. Main risks to flag to bidders: SDK-version signature drift (§3) and confirming Select/singulation interplay on the MC3330xR (§4).
## 11. References
- Zebra **RFID SDK for Android** (`com.zebra.rfid.api3`), developer.zebra.com — `PreFilters.PreFilter`, `setTagPatternBitCount`, `setBitOffset`, `FILTER_ACTION`, `Antennas.SingulationControl`.
- GS1 **EPC Tag Data Standard** — SGTIN-96 layout, GTIN<->EPC translation.
- GS1 **GCP Length Table** — company-prefix-length resolution (access restricted since Jan 2026).
*Everything to quote and build is here: exact SDK params (offset 32, bitCount 58), validated encoding, the prefix-length fix, and a concrete pass/fail test against real products.*
Bottom line, I need the hex code to place into the find function of the RFID scanner.
### Battle Sports · Zebra MC3330xR · UHF RAIN RFID
## 1. Objective
Build a small Android app for the **Zebra MC3330xR** that **inventories and locates a single exact product**, distinguishing products whose tag data differs by as little as **one bit**. Operator enters/scans a **UPC**; the app applies a precise **58-bit Gen2 Select pre-filter** so the reader responds to **only that product**, with a proximity readout to walk to it.
This does **not** exist in Zebra's free *123RFID Mobile* app, whose pre-filter UI cannot reliably apply a sub-nibble (58-bit) mask. The Zebra RFID SDK exposes the bit-level Select parameters directly — hence a custom app.
## 2. Why 58 bits
Tags are **GS1 SGTIN-96** (96-bit EPC): Header 8 (`0x30`) · Filter 3 (`1`) · Partition 3 · Company Prefix 20–40 · Item Reference 24–4 (prefix+itemref always = 44) · **Serial 38 (unique per unit)**.
The **product identity (GTIN) is the first 58 bits** (96−38). The last 38 bits are a per-unit serial. To match "this product, any serial," Select on **exactly the first 58 bits**. Hex prefixes only express whole nibbles:
- **56-bit (14 hex)** → too short; misses the distinguishing bit on near-twins.
- **60-bit (15 hex)** → reaches into the per-unit serial; matches only *some* units.
Only a **non-nibble-aligned 58-bit** mask works, and only the SDK can set it. That is the entire reason for this project.
## 3. Platform
- **Device:** Zebra MC3330xR (integrated UHF RFID, model MC333U…).
- **OS:** Android 11 (Zebra GMS build).
- **SDK:** **Zebra RFID SDK for Android**, package `com.zebra.rfid.api3` ("RFID API3"). Use the current release; **verify method signatures against that version** — the API drifts across releases.
- **Reader:** internal reader; enumerate via `Readers(context, ENUM_TRANSPORT.SERVICE_USB)` and connect the first available.
- **Build:** Android Studio, signed APK; deploy via sideload/StageNow/MDM.
## 4. Core requirement — the 58-bit pre-filter (the crux)
Apply a Gen2 Select before Inventory/Locate. Reference (verify vs your SDK version):
```java
import com.zebra.rfid.api3.*;
PreFilters preFilters = new PreFilters();
PreFilters.PreFilter pf = preFilters.new PreFilter();
pf.setMemoryBank(MEMORY_BANK.MEMORY_BANK_EPC);
pf.setBitOffset(32); // *** skip StoredCRC(16)+StoredPC(16); EPC starts at bit 32
pf.setTagPattern("30343183AC045D0"); // 58-bit pattern packed into nibbles (see note)
pf.setTagPatternBitCount(58); // *** THE KEY: compare exactly 58 bits, not 56 or 60
pf.setFilterAction(FILTER_ACTION.FILTER_ACTION_ASSERT_UNASSERT_SL); // assert SL on match, deassert others
pf.setFilterTarget(TARGET.SESSION_S1); // hold state across rounds (verify enum name)
reader.Actions.PreFilters.add(new PreFilters.PreFilter[]{ pf });
```
Then inventory querying **only SL-asserted tags** so non-matches are silenced:
```java
Antennas.SingulationControl s = reader.Config.Antennas.getSingulationControl(1);
// configure state-aware (SL) singulation consistent with FILTER_ACTION above
reader.Config.Antennas.setSingulationControl(1, s);
reader.Actions.Inventory.perform(); // only the exact product returns
```
**Pattern packing:** `tagPattern` is nibble/byte-packed; `tagPatternBitCount = 58` tells the radio to compare only the first 58 bits (the last 2 bits of the 15th nibble are ignored padding). Confirm whether your build wants a hex `String` or `byte[]`.
**Critical:** `setBitOffset(32)` and `setTagPatternBitCount(58)` make or break this. Wrong offset → mask lands on CRC/PC junk. bitCount 56 → near-twins collide.
## 5. UPC → 58-bit pattern (validated logic — reuse verbatim)
```
encode(UPC12, companyPrefixLen):
gtin14 = leftPad(UPC12, 14, '0')
indicator= gtin14[0]; middle12 = gtin14[1..12]
companyPrefix = middle12[0 .. companyPrefixLen-1]
itemRefGtin = middle12[companyPrefixLen .. end]
itemField = indicator + itemRefGtin // decimal concat
partition = {6->6,7->5,8->4,9->3,10->2,11->1,12->0}[companyPrefixLen]
cpBits = {6->20,7->24,8->27,9->30,10->34,11->37,12->40}[companyPrefixLen]
irBits = {6->24,7->20,8->17,9->14,10->10,11->7,12->4}[companyPrefixLen]
bits = bin(0x30,8) + bin(1,3) + bin(partition,3)
+ bin(int(companyPrefix),cpBits) + bin(int(itemField),irBits)
return first 58 bits of bits // Select pattern; bitCount=58, offset=32
```
### WARNING — Company-prefix-length ambiguity (must handle — real on this data)
A UPC does not carry its GS1 company-prefix length, and **this supplier encodes inconsistently** — the same brand uses **7-digit (partition 5)** on some products, **8-digit (partition 4)** on others. A pure UPC->pattern compute will sometimes pick the wrong partition and match nothing. Resolve, in priority order:
1. **Seed from a reference tag (recommended).** On first encounter, operator scans **one physical unit's RFID tag**; app decodes its EPC (reverse of §5), extracts the **actual partition + first-58-bit pattern**, stores it keyed by UPC. All later locates use the stored pattern — ground truth, ambiguity gone.
2. **GS1 GCP Length Table** lookup (access restricted since Jan 2026; budget to license/cache a copy).
3. **Compute both 7- and 8-digit candidates, try in order** (fallback).
Include an EPC-hex -> UPC+partition decoder for step 1 (also validated in scoping).
## 6. App features
1. **Product entry:** type a UPC, or scan the printed UPC barcode via the built-in imager (DataWedge -> field).
2. **Resolve pattern** per §5; if unknown, prompt to seed from a reference-tag scan.
3. **Apply pre-filter** (§4).
4. **Inventory mode:** live **count** of the exact product (near-twins excluded).
5. **Locate mode:** proximity (RSSI/%) + beeper intensifying with proximity, trigger-driven.
6. **Power control:** expose TX power (0–30 dBm) for close-range pinpointing.
7. **Session/target** handling so the filter persists across rounds.
## 7. Data model (minimum)
`products`: `upc` (PK), `gtin14`, `company_prefix_len`, `partition`, `pattern_hex` (58-bit packed), `pattern_bitcount` (=58), `bit_offset` (=32), `source` (`reference_tag`|`gcp_table`|`computed`), `verified_at`.
## 8. Acceptance test (pass/fail — use these exact values)
Two **real, different** products differing by one bit. Pass only if each is isolated.
| UPC | Encoding | 58-bit pattern (hex, packed) | bitCount | bitOffset |
|---|---|---|---|---|
| **811243044689** | 7-digit / partition 5 | `30343183AC045D0` | 58 | 32 |
| **811243044702** | 7-digit / partition 5 | `30343183AC045D8` | 58 | 32 |
Both share the **first 56 bits** (`30343183AC045D`) — they collide at 14 hex chars and first differ at **bit index 56**.
- **T1 (isolation):** with both present, load 689 -> report **only 689**, reject all 702. Repeat for 702.
- **T2 (completeness):** with N units of 689 (varying serials) -> return **all N** (matches product, not one serial).
- **T3 (negative control):** set `bitCount = 56` -> both appear (demonstrates the bug the fix resolves).
- **T4 (mixed encoding):** include one 8-digit/partition-4 product -> reference-tag seeding (§5.1) resolves it.
## 9. Deliverables
1. Signed, installable **APK** for Android 11 on the MC3330xR.
2. **Source** (Android Studio project) + build instructions.
3. **UPC->pattern encoder** and **EPC->UPC decoder** as tested modules (unit tests using §8 vectors).
4. Short **operator guide** + deployment notes.
5. Demonstrated §8 tests passing on the physical device.
## 10. Skills / effort
Required: **Android (Java/Kotlin)** + hands-on **Zebra RFID SDK for Android** experience (GS1/EPC a plus; encoding is fully specified). Scope is a focused single-purpose app (one reader, one filter type, two modes) — **days, not weeks** for an experienced dev. Main risks to flag to bidders: SDK-version signature drift (§3) and confirming Select/singulation interplay on the MC3330xR (§4).
## 11. References
- Zebra **RFID SDK for Android** (`com.zebra.rfid.api3`), developer.zebra.com — `PreFilters.PreFilter`, `setTagPatternBitCount`, `setBitOffset`, `FILTER_ACTION`, `Antennas.SingulationControl`.
- GS1 **EPC Tag Data Standard** — SGTIN-96 layout, GTIN<->EPC translation.
- GS1 **GCP Length Table** — company-prefix-length resolution (access restricted since Jan 2026).
*Everything to quote and build is here: exact SDK params (offset 32, bitCount 58), validated encoding, the prefix-length fix, and a concrete pass/fail test against real products.*
Bottom line, I need the hex code to place into the find function of the RFID scanner.