Devices

RFID EPC tags

RFID tags' EPC numbers by GS1's Tag Data Standard: SGTIN-96 from a GTIN and serial, SSCC-96, SGLN-96, GRAI-96 and GIAI-96 encoded to the hex a tag holds, decoded back with their URIs and the GTIN, SSCC or GLN inside, and a reader's inventory.

Version 1.0.0 · 8 functions · 0 crates

#barcodes#logistics#shop#devices#ids

Functions

What use epc.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use epc_sgtin, epc_sscc from epc (Quillo 0.6).

epc_sgtin(gtin: str, company_prefix_length: int, serial: int, filter: int) → stror an error

An SGTIN-96 from a GTIN (8 to 14 digits, its check digit right), the length of its GS1 company prefix, and a serial (0 to 274,877,906,943). filter: 1 for a point-of-sale item, 2 a full case, 3 a load, 0 anything else. Gives 24 hex digits.

epc_sscc(sscc: str, company_prefix_length: int, filter: int) → stror an error

An SSCC-96 from an SSCC (18 digits, its check digit right) and its company prefix length. Gives 24 hex digits.

epc_sgln(gln: str, company_prefix_length: int, extension: int, filter: int) → stror an error

An SGLN-96 from a GLN (13 digits), its company prefix length and an extension (0 for the place itself). Gives 24 hex digits.

epc_grai(company_prefix: str, asset_type: str, serial: int, filter: int) → stror an error

A GRAI-96 (a returnable asset: a crate, a keg, a pallet) from its company prefix, asset type and serial. Gives 24 hex digits.

epc_giai(company_prefix: str, asset_reference: str, filter: int) → stror an error

A GIAI-96 (a fixed asset: a machine, a laptop) from its company prefix and a numeric asset reference. Gives 24 hex digits.

epc_decode(hex: str) → anyor an error

A 96-bit EPC (24 hex digits, as a reader reports it) decoded: { scheme, filter, company_prefix, reference, serial, gtin | sscc | gln, pure_uri, tag_uri }.

epc_from_uri(uri: str) → stror an error

A tag URI (urn:epc:tag:sgtin-96:3.0614141.812345.6789, and the sscc, sgln, grai and giai forms) as its 24 hex digits.

epc_inventory(hexes: list) → list

Many reads decoded at once (a reader's inventory): each { hex, ... } as epc_decode gives it, or { hex, error }; repeats counted in reads.

Get it

$ quillo install epc               # epc.qwer into this folder$ quillo install epc --example     # and its example, epc.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/epc.qwer$ curl -O https://quillolang.org/plugins/files/epc.qwe

Then, in your program, all of it or only what you call:

main.qwe
use epc.qweruse epc_sgtin, epc_sscc from epc      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]

Example

epc.qwe
// A clothing store's RFID: the EPC to write on each item's tag from its GTIN and a serial, the// pallet's SSCC and the shop floor's GLN as tags, and a reader's inventory decoded back.////   quillo run epc.qwe      then open http://localhost:8080/encode and http://localhost:8080/inventoryuse "epc.qwer"route GET /encode = {    shirt: epc_sgtin("80614141123458", 7, 6789, 1)?,    from_uri: epc_from_uri("urn:epc:tag:sgtin-96:3.0614141.812345.6789")?,    pallet: epc_sscc("106141411234567897", 7, 2)?,    floor: epc_sgln("0614141123452", 7, 5678, 0)?,    keg: epc_grai("0614141", "12345", 400, 0)?,    laptop: epc_giai("0614141", "5678", 0)?,    bad: epc_sgtin("80614141123459", 7, 1, 1).error,}route GET /decode = epc_decode("3074257BF7194E4000001A85")?route GET /inventory = epc_inventory(["3074257BF7194E4000001A85", "3074257BF7194E4000001A85", "3174257BF4499602D2000000", "E2801160600002084F9B2B5E"])
$ quillo run epc.qwe$ curl 'localhost:8080/encode'$ curl 'localhost:8080/decode'$ curl 'localhost:8080/inventory'

Source

epc.qwer
//! RFID tags' EPC numbers by GS1's Tag Data Standard: SGTIN-96 (a product's GTIN and serial), SSCC-96 (a pallet), SGLN-96 (a place), GRAI-96 (a returnable asset) and GIAI-96 (a fixed asset) encoded to the hex an RFID printer or reader writes, and decoded back, with pure identity and tag URIs and the GTIN, SSCC or GLN inside.//! version: 1.0.0//!//! [dependencies]use serde_json::{json, Value};/// (header, scheme, [(company prefix bits, digits, reference bits, digits)] by partition, serial bits).struct Scheme {    header: u8,    name: &'static str,    parts: [(u32, usize, u32, usize); 7],    serial_bits: u32,}const SCHEMES: &[Scheme] = &[    Scheme { header: 0x30, name: "sgtin", parts: [(40, 12, 4, 1), (37, 11, 7, 2), (34, 10, 10, 3), (30, 9, 14, 4), (27, 8, 17, 5), (24, 7, 20, 6), (20, 6, 24, 7)], serial_bits: 38 },    Scheme { header: 0x31, name: "sscc", parts: [(40, 12, 18, 5), (37, 11, 21, 6), (34, 10, 24, 7), (30, 9, 28, 8), (27, 8, 31, 9), (24, 7, 34, 10), (20, 6, 38, 11)], serial_bits: 0 },    Scheme { header: 0x32, name: "sgln", parts: [(40, 12, 1, 0), (37, 11, 4, 1), (34, 10, 7, 2), (30, 9, 11, 3), (27, 8, 14, 4), (24, 7, 17, 5), (20, 6, 21, 6)], serial_bits: 41 },    Scheme { header: 0x33, name: "grai", parts: [(40, 12, 4, 0), (37, 11, 7, 1), (34, 10, 10, 2), (30, 9, 14, 3), (27, 8, 17, 4), (24, 7, 20, 5), (20, 6, 24, 6)], serial_bits: 38 },    Scheme { header: 0x34, name: "giai", parts: [(40, 12, 42, 13), (37, 11, 45, 14), (34, 10, 48, 15), (30, 9, 52, 16), (27, 8, 55, 17), (24, 7, 58, 18), (20, 6, 62, 19)], serial_bits: 0 },];fn check_digit(digits: &str) -> u32 {    let s: u32 = digits.chars().rev().enumerate().map(|(i, c)| c.to_digit(10).unwrap_or(0) * if i % 2 == 0 { 3 } else { 1 }).sum();    (10 - s % 10) % 10}/// Bits written most significant first into a 96-bit number.struct W {    v: u128,    n: u32,}impl W {    fn put(&mut self, x: u128, bits: u32) {        self.v = (self.v << bits) | (x & ((1u128 << bits) - 1));        self.n += bits;    }    fn hex(self) -> String {        format!("{:024X}", self.v << (96 - self.n))    }}fn get(v: u128, start: u32, len: u32) -> u128 {    (v >> (96 - start - len)) & ((1u128 << len) - 1)}fn digits(s: &str) -> Result<String, String> {    let d: String = s.chars().filter(|c| !c.is_whitespace() && *c != '-').collect();    if d.is_empty() || !d.chars().all(|c| c.is_ascii_digit()) {        return Err(format!("{:?}: digits only", s));    }    Ok(d)}fn partition(scheme: &Scheme, cp_len: i64) -> Result<(usize, (u32, usize, u32, usize)), String> {    scheme.parts.iter().enumerate().find(|(_, p)| p.1 as i64 == cp_len).map(|(i, p)| (i, *p)).ok_or_else(|| "a GS1 company prefix is 6 to 12 digits".to_string())}fn filter_of(f: i64) -> Result<u128, String> {    if !(0..=7).contains(&f) {        return Err("the filter value is 0 to 7 (1 point of sale item, 2 case, 3 load, ...)".into());    }    Ok(f as u128)}/// An SGTIN-96 from a GTIN (8 to 14 digits, its check digit right), the length of its GS1 company prefix, and a serial (0 to 274,877,906,943). `filter`: 1 for a point-of-sale item, 2 a full case, 3 a load, 0 anything else. Gives 24 hex digits.pub fn epc_sgtin(gtin: &str, company_prefix_length: i64, serial: i64, filter: i64) -> Result<String, String> {    let g = format!("{:0>14}", digits(gtin)?);    if g.len() != 14 || check_digit(&g[..13]).to_string() != g[13..] {        return Err(format!("{:?} is not a GTIN with a right check digit", gtin));    }    if serial < 0 || serial >= 1 << 38 {        return Err("an SGTIN-96 serial is 0 to 274877906943".into());    }    let s = &SCHEMES[0];    let (p, (cb, cd, rb, _)) = partition(s, company_prefix_length)?;    // The item reference is the indicator digit, then the GTIN's digits after the company prefix.    let cp: u128 = g[1..1 + cd].parse().unwrap();    let item: u128 = format!("{}{}", &g[..1], &g[1 + cd..13]).parse().unwrap();    let mut w = W { v: 0, n: 0 };    w.put(0x30, 8);    w.put(filter_of(filter)?, 3);    w.put(p as u128, 3);    w.put(cp, cb);    w.put(item, rb);    w.put(serial as u128, 38);    Ok(w.hex())}/// An SSCC-96 from an SSCC (18 digits, its check digit right) and its company prefix length. Gives 24 hex digits.pub fn epc_sscc(sscc: &str, company_prefix_length: i64, filter: i64) -> Result<String, String> {    let d = digits(sscc)?;    if d.len() != 18 || check_digit(&d[..17]).to_string() != d[17..] {        return Err(format!("{:?} is not an SSCC with a right check digit", sscc));    }    let s = &SCHEMES[1];    let (p, (cb, cd, rb, _)) = partition(s, company_prefix_length)?;    let cp: u128 = d[1..1 + cd].parse().unwrap();    let serial: u128 = format!("{}{}", &d[..1], &d[1 + cd..17]).parse().unwrap();    let mut w = W { v: 0, n: 0 };    w.put(0x31, 8);    w.put(filter_of(filter)?, 3);    w.put(p as u128, 3);    w.put(cp, cb);    w.put(serial, rb);    w.put(0, 24);    Ok(w.hex())}/// An SGLN-96 from a GLN (13 digits), its company prefix length and an extension (0 for the place itself). Gives 24 hex digits.pub fn epc_sgln(gln: &str, company_prefix_length: i64, extension: i64, filter: i64) -> Result<String, String> {    let d = digits(gln)?;    if d.len() != 13 || check_digit(&d[..12]).to_string() != d[12..] {        return Err(format!("{:?} is not a GLN with a right check digit", gln));    }    if extension < 0 || extension >= 1 << 41 {        return Err("an SGLN-96 extension is 0 to 2199023255551".into());    }    let s = &SCHEMES[2];    let (p, (cb, cd, rb, _)) = partition(s, company_prefix_length)?;    let mut w = W { v: 0, n: 0 };    w.put(0x32, 8);    w.put(filter_of(filter)?, 3);    w.put(p as u128, 3);    w.put(d[..cd].parse().unwrap(), cb);    w.put(if cd < 12 { d[cd..12].parse().unwrap() } else { 0 }, rb);    w.put(extension as u128, 41);    Ok(w.hex())}/// A GRAI-96 (a returnable asset: a crate, a keg, a pallet) from its company prefix, asset type and serial. Gives 24 hex digits.pub fn epc_grai(company_prefix: &str, asset_type: &str, serial: i64, filter: i64) -> Result<String, String> {    let cp = digits(company_prefix)?;    let s = &SCHEMES[3];    let (p, (cb, _, rb, rd)) = partition(s, cp.len() as i64)?;    let at = if rd == 0 { String::new() } else { digits(asset_type)? };    if at.len() != rd {        return Err(format!("with a {}-digit company prefix the asset type has {} digits", cp.len(), rd));    }    if serial < 0 || serial >= 1 << 38 {        return Err("a GRAI-96 serial is 0 to 274877906943".into());    }    let mut w = W { v: 0, n: 0 };    w.put(0x33, 8);    w.put(filter_of(filter)?, 3);    w.put(p as u128, 3);    w.put(cp.parse().unwrap(), cb);    w.put(if rd == 0 { 0 } else { at.parse().unwrap() }, rb);    w.put(serial as u128, 38);    Ok(w.hex())}/// A GIAI-96 (a fixed asset: a machine, a laptop) from its company prefix and a numeric asset reference. Gives 24 hex digits.pub fn epc_giai(company_prefix: &str, asset_reference: &str, filter: i64) -> Result<String, String> {    let cp = digits(company_prefix)?;    let r = digits(asset_reference)?;    let s = &SCHEMES[4];    let (p, (cb, _, rb, _)) = partition(s, cp.len() as i64)?;    let rv: u128 = r.parse().map_err(|_| "the asset reference is too long".to_string())?;    if rv >= 1u128 << rb || r.starts_with('0') && r.len() > 1 {        return Err(format!("with a {}-digit company prefix the asset reference fits {} bits and has no leading zero", cp.len(), rb));    }    let mut w = W { v: 0, n: 0 };    w.put(0x34, 8);    w.put(filter_of(filter)?, 3);    w.put(p as u128, 3);    w.put(cp.parse().unwrap(), cb);    w.put(rv, rb);    Ok(w.hex())}/// A 96-bit EPC (24 hex digits, as a reader reports it) decoded: `{ scheme, filter, company_prefix, reference, serial, gtin | sscc | gln, pure_uri, tag_uri }`.pub fn epc_decode(hex: &str) -> Result<serde_json::Value, String> {    let h: String = hex.chars().filter(|c| !c.is_whitespace()).collect();    let h = h.trim_start_matches("0x").trim_start_matches("0X");    if h.len() != 24 || !h.chars().all(|c| c.is_ascii_hexdigit()) {        return Err("a 96-bit EPC is 24 hex digits".into());    }    let v = u128::from_str_radix(h, 16).unwrap();    let header = get(v, 0, 8) as u8;    let s = SCHEMES.iter().find(|s| s.header == header).ok_or_else(|| format!("header {:02X} is not SGTIN-96, SSCC-96, SGLN-96, GRAI-96 or GIAI-96", header))?;    let filter = get(v, 8, 3) as u8;    let p = get(v, 11, 3) as usize;    if p > 6 {        return Err("partition 7 does not exist: not a valid EPC".into());    }    let (cb, cd, rb, rd) = s.parts[p];    let cp = format!("{:0w$}", get(v, 14, cb), w = cd);    let rv = get(v, 14 + cb, rb);    let reference = if rd == 0 { String::new() } else if s.name == "giai" { rv.to_string() } else { format!("{:0w$}", rv, w = rd) };    if cp.len() != cd || reference.len() > rd.max(1) && s.name != "giai" {        return Err("the company prefix or reference has more digits than its partition allows".into());    }    let serial = if s.serial_bits > 0 { Some(get(v, 14 + cb + rb, s.serial_bits)) } else { None };    let mut out = json!({ "scheme": format!("{}-96", s.name), "filter": filter, "partition": p, "company_prefix": cp, "reference": reference, "serial": serial.map(|x| x.to_string()) });    let (pure, tag) = match s.name {        "sgtin" => {            let g13 = format!("{}{}{}", &reference[..1], cp, &reference[1..]);            out["gtin"] = json!(format!("{}{}", g13, check_digit(&g13)));            (format!("urn:epc:id:sgtin:{}.{}.{}", cp, reference, serial.unwrap()), format!("urn:epc:tag:sgtin-96:{}.{}.{}.{}", filter, cp, reference, serial.unwrap()))        }        "sscc" => {            let s17 = format!("{}{}{}", &reference[..1], cp, &reference[1..]);            out["sscc"] = json!(format!("{}{}", s17, check_digit(&s17)));            (format!("urn:epc:id:sscc:{}.{}", cp, reference), format!("urn:epc:tag:sscc-96:{}.{}.{}", filter, cp, reference))        }        "sgln" => {            let g12 = format!("{}{}", cp, reference);            out["gln"] = json!(format!("{}{}", g12, check_digit(&g12)));            (format!("urn:epc:id:sgln:{}.{}.{}", cp, reference, serial.unwrap()), format!("urn:epc:tag:sgln-96:{}.{}.{}.{}", filter, cp, reference, serial.unwrap()))        }        "grai" => (format!("urn:epc:id:grai:{}.{}.{}", cp, reference, serial.unwrap()), format!("urn:epc:tag:grai-96:{}.{}.{}.{}", filter, cp, reference, serial.unwrap())),        _ => (format!("urn:epc:id:giai:{}.{}", cp, reference), format!("urn:epc:tag:giai-96:{}.{}.{}", filter, cp, reference)),    };    out["pure_uri"] = json!(pure);    out["tag_uri"] = json!(tag);    Ok(out)}/// A tag URI (`urn:epc:tag:sgtin-96:3.0614141.812345.6789`, and the sscc, sgln, grai and giai forms) as its 24 hex digits.pub fn epc_from_uri(uri: &str) -> Result<String, String> {    let rest = uri.trim().strip_prefix("urn:epc:tag:").ok_or("a tag URI starts urn:epc:tag:")?;    let (scheme, body) = rest.split_once(':').ok_or("no scheme")?;    let p: Vec<&str> = body.split('.').collect();    let f = |i: usize| -> Result<i64, String> { p.get(i).ok_or("a part is missing".to_string())?.parse::<i64>().map_err(|_| format!("{:?} is not a number", p[i])) };    match scheme {        "sgtin-96" if p.len() == 4 => {            let (cp, item) = (p[1], p[2]);            let g13 = format!("{}{}{}", &item[..1.min(item.len())], cp, item.get(1..).unwrap_or(""));            epc_sgtin(&format!("{}{}", g13, check_digit(&g13)), cp.len() as i64, f(3)?, f(0)?)        }        "sscc-96" if p.len() == 3 => {            let s17 = format!("{}{}{}", &p[2][..1.min(p[2].len())], p[1], p[2].get(1..).unwrap_or(""));            epc_sscc(&format!("{}{}", s17, check_digit(&s17)), p[1].len() as i64, f(0)?)        }        "sgln-96" if p.len() == 4 => {            let g12 = format!("{}{}", p[1], p[2]);            epc_sgln(&format!("{}{}", g12, check_digit(&g12)), p[1].len() as i64, f(3)?, f(0)?)        }        "grai-96" if p.len() == 4 => epc_grai(p[1], p[2], f(3)?, f(0)?),        "giai-96" if p.len() == 3 => epc_giai(p[1], p[2], f(0)?),        _ => Err(format!("{:?}: sgtin-96, sscc-96, sgln-96, grai-96 or giai-96 with its parts", scheme)),    }}/// Many reads decoded at once (a reader's inventory): each `{ hex, ... }` as `epc_decode` gives it, or `{ hex, error }`; repeats counted in `reads`.pub fn epc_inventory(hexes: Vec<String>) -> Vec<serde_json::Value> {    let mut seen: Vec<(String, Value)> = vec![];    for h in hexes {        let k = h.trim().to_uppercase();        if let Some(e) = seen.iter_mut().find(|(x, _)| *x == k) {            e.1["reads"] = json!(e.1["reads"].as_i64().unwrap_or(1) + 1);            continue;        }        let mut v = epc_decode(&k).unwrap_or_else(|e| json!({ "error": e }));        v["hex"] = json!(k);        v["reads"] = json!(1);        seen.push((k, v));    }    seen.into_iter().map(|(_, v)| v).collect()}
Barcodes barcode.qwer · 1.0.0EAN-13, EAN-8, UPC-A, Code 128 and Code 39 barcodes as SVG or PNG, with the digits underneath and check digits worked out.barcodeslabelsimagesshop6 views · 5 downloadsG-code for 3D printers gcode.qwer · 1.0.0G-code from 3D printers and CNC machines read: print time with acceleration, filament length, weight and cost, the print's size, layers and their heights, temperatures and the slicer's notes, and a layer drawn as SVG.filesdevicescalculatorssvg6 views · 7 downloadsModbus frames modbus.qwer · 1.0.0Modbus RTU and TCP frames built and read without a device: register and coil requests, answers and exceptions explained, CRC-16 and MBAP headers, registers turned into integers, floats and text in any word order and back, and a TCP client for a real PLC or meter.iotsensorsdevicesbinarynetwork8 views · 10 downloadsNMEA GPS nmea.qwer · 1.0.0NMEA 0183 sentences from GPS receivers read: position, time, speed, course, altitude, satellites and fix quality, with checksums checked.gpsdevicesmarinetrackingimport7 views · 8 downloadsOBD-II obd2.qwer · 1.0.0OBD-II answers from an ELM327 adapter decoded: speed, RPM, temperatures, load, fuel level and voltage, trouble codes such as P0133, and the PIDs a car supports.carsdevicesfleetsensorsiot8 views · 8 downloadsReceipt printers escpos.qwer · 1.0.0Receipts for ESC/POS thermal printers: aligned and bold lines, two-column rows, QR codes and barcodes, the cutter and the cash drawer, as base64, a file, or sent to port 9100.printingreceiptsposdevices7 views · 6 downloads
Thread
68Useful? Like it, or add to its thread below.

Thread 0 posts

On RFID EPC tags: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.