/// <summary> /// Encode the Tag EPC URI in SGTIN scheme into tag EPC memory bank. /// </summary> /// <param name="reader">Reference to the reader</param> /// <param name="tag">Reference to the singulated tag (reader-specific datum)</param> /// <param name="uri">The Tag URI</param> /// <returns>True if succeeded</returns> public static bool WriteEPC(this IReaderLowLevel reader, object tag, TagEPCURI_SGTIN uri) { // try to encode the EPC Tag URI // see 14.5.1, table 14-2 // algorithm: 14.3.3 byte[] res_control = new byte[4]; byte[] res = new byte[12]; res_control[0] = 0; // CRC-16 res_control[1] = 0; // CRC-16 /* * The number of bits, N, in the EPC binary encoding determined * in Step 2 above, divided by 16, and rounded up to the next * higher integer if N was not a multiple of 16 */ var nbits = 12 * 8; // full length of PC+EPC data, in bits int nbit_value = (nbits + 15) / 16; var UMI = true; // indicates if user memory bank is present var XI = false; // indicates if XPC is present var toggle = false; // indicates if bits 0x18-0x1F contain attribute bits and the remainder of EPC bank contains a binary encoded EPC res_control[2] = // var pc_epc_len = ((epc[2] >> 3) & 0x1F) * 2; // full length of PC+EPC data, in bytes (byte)((nbit_value << 3) | (UMI? 0x4 : 0) | (XI? 0x2 : 0) | (toggle? 0x1 : 0)); res_control[3] = 0; //Console.WriteLine("Control: {0}", BitConverter.ToString(res_control)); // SGTIN-96 // EPC header 1 byte = 00110000 (bin) // filter 3 bits (3 bit integer) // partition 3 bits (3 bit integer, see below) // GS1 Company Prefix 20-40 bits // Item Ref 24-4 bits // Serial 38 bits // write the header res[0] = 0x30; // write the filter res[1] = (byte)(res[1] | ((uri.Filter & 0x7) << 5)); var uri0 = uri.Identity.URI; byte partition = 0; var C = uri.Identity.GS1CompanyPrefix; var D = uri.Identity.ItemRef; // value P (partition value, 3 bits) // value C (of M-bit width) var Cdigits = uri.Identity.GS1CompanyPrefixLength; // value D (of N-bit width) var Ddigits = uri.Identity.ItemRefLength; // NOTE: Cdigits + Ddigits must be 13 var sum = Cdigits + Ddigits; // there's a complication // a GTIN might be: // - GTIN-8 (8 digits) // - GTIN-12 (12 digits) // - GTIN-13 (13 digits) // - GTIN-14 (14 digits) // first of all, how to convert GTIN-8 to GTIN-12? // what about anything else? should we use strings instead of long integers? /* * see: https://www.gs1us.org/resources/standards/company-prefix * - GS1 Company Prefix is a string of digits * - GS1 Company Prefix Length is between 7 and 11 digits * - GS1 Company Prefix Length provides capacity in emitting fresh GTINs * - prefix length = 7, capacity = 100 000 * - prefix length = 8, capacity = 10 000 * * A GS1 Company Prefix and an Item Reference Number comprise a GTIN. * * http://www.envioag.com/wp-content/uploads/2013/05/GTIN_070313.pdf */ if (Cdigits == 12 && Ddigits == 1) { partition = 0; // new SGTINPartInfo() { bitsM = 40, digitsL = 12, bitsN = 4, digits = 1 }, res[1] |= (byte)((C >> 38) & 0x3); res[2] |= (byte)((C >> 30) & 0xFF); res[3] |= (byte)((C >> 22) & 0xFF); res[4] |= (byte)((C >> 14) & 0xFF); res[5] |= (byte)((C >> 6) & 0xFF); res[6] |= (byte)(((C >> 0) & 0x3F) << 2); res[6] |= (byte)((D >> 2) & 0x3); res[7] |= (byte)(((D >> 0) & 0x3) << 6); } else if (Cdigits == 11 && Ddigits == 2) { // 1 return(false); } else if (Cdigits == 10 && Ddigits == 3) { partition = 2; res[1] |= ((byte)((C >> 32) & 0x03)); res[2] |= ((byte)((C >> 24) & 0xFF)); res[3] |= ((byte)((C >> 16) & 0xFF)); res[4] |= ((byte)((C >> 8) & 0xFF)); res[5] |= ((byte)((C >> 0) & 0xFF)); res[6] |= (byte)(D >> 2); res[7] |= (byte)(((D >> 0) & 0x03) << 6); } else if (Cdigits == 9 && Ddigits == 4) { // 3 return(false); } else if (Cdigits == 8 && Ddigits == 5) { // 4 return(false); } else if (Cdigits == 7 && Ddigits == 6) { partition = 5; res[1] |= ((byte)((C >> 22) & 0x03)); res[2] |= ((byte)((C >> 14) & 0xFF)); res[3] |= ((byte)((C >> 6) & 0xFF)); res[4] |= ((byte)(((C >> 0) & 0x3F) << 2)); res[4] |= ((byte)((D >> 18) & 0x03)); res[5] |= ((byte)((D >> 10) & 0xFF)); res[6] |= ((byte)((D >> 2) & 0xFF)); res[7] |= ((byte)(((D >> 0) & 0x03) << 6)); } else if (Cdigits == 6 && Ddigits == 7) { // 6 return(false); } else { // unknown partition return(false); } res[1] = (byte)(res[1] | ((partition & 0x7) << 2)); var S = uri.Identity.SerialNr; res[7] = (byte)(res[7] | (byte)((S >> 32) & 0x3F)); res[8] = (byte)(res[8] | (byte)((S >> 24) & 0xFF)); res[9] = (byte)(res[9] | (byte)((S >> 16) & 0xFF)); res[10] = (byte)(res[10] | (byte)((S >> 8) & 0xFF)); res[11] = (byte)(res[11] | (byte)((S >> 0) & 0xFF)); /* * var res_hexstring = BitConverter.ToString(res); * Console.WriteLine("result of WriteEPC: {0}", res_hexstring); */ if (reader.ChunkSize > 0) { // try writing in blocks of ChunkSize var csize = reader.ChunkSize; var chunk = new byte[csize]; var beg = 0; var ofs = 4; while (beg < res.Length) { Buffer.BlockCopy(res, beg, chunk, 0, csize); if (!reader.WriteBytes(tag, 0x01, ofs, csize, chunk)) { return(false); } ofs += csize; beg += csize; } return(true); } else { // skip the first 4 bytes return(reader.WriteBytes(tag, 0x01, 4, res.Length, res)); } }
/// <summary> /// Generate an Tag EPC URI based on a given template and the TID. /// </summary> /// <param name="tid">TID of the tag that the EPC is to be generated for.</param> /// <param name="epc">Template EPC, containing all non-serial information (e.g. GS-1 company prefix, item reference, etc.)</param> /// <param name="res">The resulting EPC</param> /// <returns>true if succeeded</returns> public static bool GenerateEPC(ref TID tid, ref TagEPCURI_SGTIN epc, out TagEPCURI_SGTIN res) { res = default(TagEPCURI_SGTIN); var serial = tid.Serial; if (serial == null || serial.Length == 0) { return(false); } var MCS_identifier = (tid.TMN >> 5) & 0x3F; var MCS_product = (tid.TMN >> 3) & 0x7; var MCS_version = tid.TMN & 0x7; // for Higgs-3: identifier 0x10, product 0x2, version 0x2 // for Monza: identifier 0x4, product 0, version 5 // System.Windows.Forms.MessageBox.Show( // string.Format("MCS: identifier {0:X}, product {1:X}, version {2:X}", MCS_identifier, MCS_product, MCS_version) // ); // extract 35 least significant bits of desired serial number // from Serial var start = 0; if (serial.Length == 8) // 64 bits { switch (tid.TMN) { case 0x412: // Alien Higgs-3: 64-bits of uniqueness // but we only want 35 bits, and since they do // support MCS, it should be OK if (tid.MDID == 0x3) // vendor: Alien, chip: Higgs-3 { start = 3; } else { return(false); // untested } break; default: return(false); // untested } } else if (serial.Length == 6) // 48 bits { switch (tid.TMN) { // TMN (hex) for Monza-4 chips (source: Monza Serialization, above) case 0x100: // Monza 4D case 0x10C: // Monza 4E case 0x105: // Monza 4QT // on Impinj Monza-4 chips, this gives the sought-for uniqueness // or does it??? if (tid.MDID == 0x801) // vendor: Impinj { start = 0; } else { start = 1; } break; case 0x412: // Alien Higgs-3? 64-bits of uniqueness if (tid.MDID == 0x3) // vendor: Alien, chip: Higgs-3 // NOTE: still, I found two tags which have the same UTID, byte-by-byte { start = 1; } else { start = 1; } break; default: start = 1; // untested break; } } else if (serial.Length == 5) // 40 bits (e.g. some NXP tags) { start = 0; } else { return(false); } var newserial = 0L; newserial |= ((long)(serial[start + 0] & 0x07) << 32); newserial |= ((long)(serial[start + 1] & 0xFF) << 24); newserial |= ((long)(serial[start + 2] & 0xFF) << 16); newserial |= ((long)(serial[start + 3] & 0xFF) << 8); newserial |= ((long)(serial[start + 4] & 0xFF) << 0); // put the 3 bits identifying the chip vendor int vendor = 0; var MDID = tid.MDID & ~0x800; // remove the XTID indicator switch (MDID) { case 0x003: // Alien vendor = 0x06; // 110 break; case 0x001: // Impinj vendor = 0x05; // 101 break; case 0x006: // NXP vendor = 0x07; // 111 break; default: return(false); // unknown vendor, they may not support MCS! } newserial |= ((long)(vendor & 0x07) << 35); Debug.WriteLine(string.Format("serial: {0}, starting at {1}, newserial: {2}", BitConverter.ToString(serial), start, newserial)); res = epc; // C# needs some Rust-style borrowing stuff for value types! // (or, some linear types would be even nicer, as they are simpler) var identity = epc.Identity; Debug.WriteLine(string.Format("identity EPC before serial assignment {0}", identity.URI)); identity.SetSerial(newserial); res.Identity = identity; Debug.WriteLine(string.Format("identity EPC after serial assignment {0}", identity.URI)); return(true); }
/// <summary> /// Decode the EPC memory bank, which is assumed to be SGTIN-192, and return the Tag URI. /// </summary> /// <param name="reader">Reference to the reader that is used to obtain the TID memory bank contents</param> /// <param name="tag">Reference to the singulated tag (reader-specific datum)</param> /// <param name="uri">The resulting Tag URI</param> /// <returns>Success or failure</returns> public static DecodeError ReadEPC_SGTIN(this IReaderLowLevel reader, object tag, out TagEPCURI_SGTIN uri) { uri = default(TagEPCURI_SGTIN); // see also: http://www.rfidjournal.net/masterPresentations/rfid_live2012/np/traub_apr3_230_ITProf.pdf // what about the EPC? // bit 0x17 of EPC is the toggle // - we need to use SGTIN-96, and encode it // - pure identity URI (e.g. urn:epc:id:sgtin:0614141.100743.401) // - independent of RFID, so this is what the business apps should use // - tag URI (e.g. urn:epc:tag:sgtin-96:3.0614141.100743.401) <-- contains control info of EPC memory bank as well // - used when reading from a tag where the control info is of interest // - used when writing to the EPC memory bank of an RFID tag, in order to fully specify the contents to be written // - there exists 1:1 mapping between tag URI and binary representation // - what's the barcode equivalent? // barcode: // (01) 1 0614141 12345 2 (21) 401 // ^^^^ not included // pure identity URI: // urn:epc:id:sgtin:0614141.112345.401 // - also, need to learn how to encode SGTIN-96 to binary form and back! // // also, we might want to use the GS1 GIAI schema // - http://www.gs1.org/docs/idkeys/GS1_GIAI_Executive_Summary.pdf // - 1 2 ... n n+1 n+2 ... <= 30 // GS1 company prefix| individual asset reference // (numeric) | (alphanumeric) // // what's in SGTIN? e.g. urn:epc:id:sgtin:0614141.112345.400 // urn:epc:id:sgtin:CompanyPrefix.ItemRefAndIndicator.SerialNumber // - company prefix (assigned by GS1 to a managing entity or its delegates) // - item ref and indicator (assigned by the managing entity to a particular object class) // - serial number (assigned by the managing entity to an individual object) // what's in GIAI? e.g. urn:epc:id:giai:0614141.12345400 // urn:epc:id:giai:CompanyPrefix.IndividulAssetReference // - company prefix (assigned by GS1 to a managing entity) // - individual asset reference (assigned uniquely by the managing entity to a specific asset) /* * how to assign? * * what's in the EPC * - 2 bytes of CRC (we don't usually write these, right?) * - 2 bytes: length (5 bits), UMI (1 bit), XI (1 bit), T (1 bit), attribs (8 bits) * - length: nr of words for PC and EPC, combined * - 2 bytes: EPC header (1 byte), filter (3 bits), partition (3 bits), GTIN (2 bits) * - 2 bytes: GTIN * - 2 bytes: GTIN * - 2 bytes: GTIN (10 bits), serial (6 bits) * - 2 bytes: serial * - 2 bytes: serial * 16 bytes in total */ // see also: http://www.rfidjournal.net/masterPresentations/rfid_live2012/np/traub_apr3_230_ITProf.pdf // what about the EPC? // bit 0x17 of EPC is the toggle // - we need to use SGTIN-96, and encode it // - pure identity URI (e.g. urn:epc:id:sgtin:0614141.100743.401) // - independent of RFID, so this is what the business apps should use // - tag URI (e.g. urn:epc:tag:sgtin-96:3.0614141.100743.401) <-- contains control info of EPC memory bank as well // - used when reading from a tag where the control info is of interest // - used when writing to the EPC memory bank of an RFID tag, in order to fully specify the contents to be written // - there exists 1:1 mapping between tag URI and binary representation // - what's the barcode equivalent? // barcode: // (01) 1 0614141 12345 2 (21) 401 // ^^^^ not included // pure identity URI: // urn:epc:id:sgtin:0614141.112345.401 // - also, need to learn how to encode SGTIN-96 to binary form and back! // // also, we might want to use the GS1 GIAI schema // - http://www.gs1.org/docs/idkeys/GS1_GIAI_Executive_Summary.pdf // - 1 2 ... n n+1 n+2 ... <= 30 // GS1 company prefix| individual asset reference // (numeric) | (alphanumeric) // // what's in SGTIN? e.g. urn:epc:id:sgtin:0614141.112345.400 // urn:epc:id:sgtin:CompanyPrefix.ItemRefAndIndicator.SerialNumber // - company prefix (assigned by GS1 to a managing entity or its delegates) // - item ref and indicator (assigned by the managing entity to a particular object class) // - serial number (assigned by the managing entity to an individual object) // what's in GIAI? e.g. urn:epc:id:giai:0614141.12345400 // urn:epc:id:giai:CompanyPrefix.IndividulAssetReference // - company prefix (assigned by GS1 to a managing entity) // - individual asset reference (assigned uniquely by the managing entity to a specific asset) /* * how to assign? * * what's in the EPC * - 2 bytes of CRC (we don't usually write these, right?) * - 2 bytes: length (5 bits), UMI (1 bit), XI (1 bit), T (1 bit), attribs (8 bits) * - length: nr of words for PC and EPC, combined * - 2 bytes: EPC header (1 byte), filter (3 bits), partition (3 bits), GTIN (2 bits) * - 2 bytes: GTIN * - 2 bytes: GTIN * - 2 bytes: GTIN (10 bits), serial (6 bits) * - 2 bytes: serial * - 2 bytes: serial * 16 bytes in total */ EPC_PC pc; var rr = ReadEPC_PC(reader, tag, out pc); if (rr != DecodeError.None) { return(rr); } var crc16 = pc.Crc16; var pc_epc_len = pc.PC_EPC_Length; var UMI = pc.UMI; var XI = pc.XI; var toggle = pc.Toggle; var attribs = pc.Attribs; // read full length of EPC (12 bytes or more) byte[] epc_payload = new byte[pc_epc_len]; { const int chunkSize = 4; int sizeRead = 0; while (sizeRead < pc_epc_len) { byte[] chunk; if (!reader.ReadBytes(tag, 0x01, 4 + sizeRead, chunkSize, out chunk)) { return(DecodeError.IO); } Buffer.BlockCopy(chunk, 0, epc_payload, sizeRead, chunkSize); sizeRead += chunkSize; } } //var epc_payload_hex = BitConverter.ToString(epc_payload); //System.Windows.Forms.MessageBox.Show(BitConverter.ToString(epc_payload)); // SGTIN-96 var epc_header = epc_payload[0]; // must be 0x30 if that's SGTIN-96! if (epc_header != 0x30) { return(DecodeError.Invalid); // NOT SGTIN-96! } if (pc_epc_len < 10 /*96 bits*/) { return(DecodeError.Invalid); // wrong length of EPC! (see table 14.1 in EPC TDS 1.9) } var filter = (epc_payload[1] >> 5) & 0x7; var partition = (epc_payload[1] >> 2) & 0x7; // 14.4.3 var C = 0L; // company prefix var Cdigits = 0; var D = 0L; // indicator/item reference var Ddigits = 0; var S = 0L; // Consider these M bits to be an unsigned binary integer, C. // The value of C must be less than 10^L, where L is the value // specified in the “GS1 Company Prefix Digits (L)” column of // the matching partition table row. // // There are N bits remaining in the input bit string, where // N is the value specified in the “Other Field Bits” column // of the matching partition table row. Consider these N bits // to be an unsigned binary integer, D. The value of D must be // less than 10^K, where K is the value specified in the // “Other Field Digits (K)” column of the matching partition // table row. Note that if K = 0, then the value of D must be zero. switch (partition) { case 0: // new SGTINPartInfo() { bitsM = 40, digitsL = 12, bitsN = 4, digits = 1 }, C |= ((long)(epc_payload[1] & 0x3) << 38); C |= ((long)(epc_payload[2] & 0xFF) << 30); C |= ((long)(epc_payload[3] & 0xFF) << 22); C |= ((long)(epc_payload[4] & 0xFF) << 14); C |= ((long)(epc_payload[5] & 0xFF) << 6); C |= ((long)(((epc_payload[6] >> 2) & 0x3F)) << 0); Cdigits = 12; if ((ulong)C >= IntPow10((byte)Cdigits)) { return(DecodeError.Invalid); // something is wrong with it! } D = ((((epc_payload[6] & 0x3) << 2) | ((epc_payload[7] >> 6) & 0x3)) & 0xFF) << 0; Ddigits = 1; if ((ulong)D >= IntPow10((byte)1)) { return(DecodeError.Invalid); } break; /* * case 1: * //{1, new SGTINPartInfo() { bitsM = 37, digitsL = 11, bitsN = 7, digits = 2 }}, * C |= ((long)(epc_payload[1] & 0x03) << 35); * C |= ((long)(epc_payload[2] & 0xFF) << 27); * C |= ((long)(epc_payload[3] & 0xFF) << 19); * C |= ((long)(epc_payload[4] & 0xFF) << 11); * C |= ((long)(epc_payload[5] & 0xFF) << 3); * C |= ((long)(((epc_payload[6] >> 5) & 0x07)) << 0); * Cdigits = 11; * if ((ulong)C >= IntPow10((byte)Cdigits)) * continue; // something is wrong with it! * * D |= ((long)(epc_payload[6] & 0x07) << 3); // 3 bits * D |= ((((long)epc_payload[7] >> 4) & 0x0F) << 0); // 4 bits * Ddigits = 2; * if ((ulong)D >= IntPow10((byte)Ddigits)) * continue; * * break;*/ case 2: //{2, new SGTINPartInfo() { bitsM = 34, digitsL = 10, bitsN = 10, digits = 3 }}, C |= ((long)(epc_payload[1] & 0x03) << 32); C |= ((long)(epc_payload[2] & 0xFF) << 24); C |= ((long)(epc_payload[3] & 0xFF) << 16); C |= ((long)(epc_payload[4] & 0xFF) << 8); C |= ((long)(epc_payload[5] & 0xFF) << 0); Cdigits = 10; if ((ulong)C >= IntPow10((byte)Cdigits)) { return(DecodeError.Invalid); // something is wrong with it! } D |= ((long)epc_payload[6] << 2); D |= ((long)((epc_payload[7] >> 6) & 0x03) << 0); Ddigits = 3; if ((ulong)D >= IntPow10((byte)Ddigits)) { return(DecodeError.Invalid); } break; /* * case 3: * // {3, new SGTINPartInfo() { bitsM = 30, digitsL = 9, bitsN = 14, digits = 4 }}, * break; * case 4: * // {4, new SGTINPartInfo() { bitsM = 30, digitsL = 9, bitsN = 14, digits = 4 }}, */ case 5: // {5, new SGTINPartInfo() { bitsM = 24, digitsL = 7, bitsN = 20, digits = 6 }}, C |= ((long)(epc_payload[1] & 0x03) << 22); C |= ((long)(epc_payload[2] & 0xFF) << 14); C |= ((long)(epc_payload[3] & 0xFF) << 6); C |= ((((long)epc_payload[4] >> 2) & 0x3F) << 0); Cdigits = 7; if ((ulong)C >= IntPow10((byte)Cdigits)) { return(DecodeError.Invalid); // something is wrong with it! } D |= ((long)(epc_payload[4] & 0x03) << 18); D |= ((long)(epc_payload[5] & 0xFF) << 10); D |= ((long)(epc_payload[6] & 0xFF) << 2); D |= ((long)((epc_payload[7] >> 6) & 0x03) << 0); Ddigits = 6; if ((ulong)D >= IntPow10((byte)Ddigits)) { return(DecodeError.Invalid); } break; /* * case 6: * //{6, new SGTINPartInfo() { bitsM = 20, digitsL = 6, bitsN = 24, digits = 7}}, * C |= ((long)(epc_payload[1] & 0x03) << 18); * C |= ((long)(epc_payload[2] & 0xFF) << 10); * C |= ((long)(epc_payload[3] & 0xFF) << 2); * C |= ((((long)epc_payload[4] >> 2) & 0x3F) << 0); * Cdigits = 6; * if ((ulong)C >= IntPow10((byte)Cdigits)) * continue; // something is wrong with it! * * D |= ((long)(epc_payload[4] & 0x03) << 22); * D |= ((long)(epc_payload[5] & 0xFF) << 14); * D |= ((long)(epc_payload[6] & 0xFF) << 6); * D |= ((long)((epc_payload[7] >> 2) & 0x03) << 0); * Ddigits = 7; * if ((ulong)D >= IntPow10((byte)Ddigits)) * continue; // something is wrong with it! * break;*/ default: return(DecodeError.Invalid); // unable to parse this partition } // serial: 38 bits // NOTE: must be less than 274,877,906,944 // 6 bits, and then some more S |= ((long)(epc_payload[7] & 0x3F) << 32); S |= ((long)(epc_payload[8] & 0xFF) << 24); S |= ((long)(epc_payload[9] & 0xFF) << 16); S |= ((long)(epc_payload[10] & 0xFF) << 8); S |= ((long)(epc_payload[11] & 0xFF) << 0); uri.Filter = (byte)filter; uri.Identity = new IdentityEPCURI_SGTIN() { GS1CompanyPrefix = C, ItemRef = D, GS1CompanyPrefixLength = Cdigits, ItemRefLength = Ddigits, SerialNr = S }; return(DecodeError.None); }