简介:面向供应链、仓储及物联网开发人员的C#源码包,聚焦UPC编码批量转换SGTIN96,也可作为学习EPC编码规则的参考。包内包含完整Visual Studio解决方案,工程文件、C#源码、配置文件及依赖库一目了然,便于直接调试和二次开发。压缩包共177个文件,以dll、xml、cs为主,另有config、exe、pdb等辅助文件,整体约82.36MB,目录结构清晰,适合对照源码理解GS1公司前缀扩展、单品标识截取、序列号填充及校验位计算等关键步骤。已有1543人学习/下载。通过实际运行和阅读代码,读者可掌握SGTIN96位域布局、UPC到EPC的位运算转换逻辑,并能将转换函数集成到库存管理或追溯系统中,提升单品级追踪能力。
1. SGTIN96 到底解决什么问题:UPC 光学码与 EPC 射频标的对接
UPC-A 是贴在商品外包装上的 12 位光学条码,扫码枪看到的是商品类目,不是单件商品。RFID 标签里需要的是 EPC(电子产品代码),而 SGTIN96 是 EPC 体系里专门给贸易项目做序列化标识的 96 位紧凑格式。UPC 转 EPC 这件事,在零售 RFID 项目里几乎绕不开:门店要盘点、防损、自动结算,后台系统却只认 UPC 编码;贴标机、RFID 打印机和读写器又要拿到 24 位十六进制的 EPC 值。SGTIN96 的价值是让同一个商品既能沿用原有 UPC/GTIN 主数据,又能在 EPC 里追加唯一序列号。这篇文章把从 UPC-A 到 SGTIN-96 EPC 的完整换算路径讲清楚,附带可直接抄的 Python 脚本和排错方法。
2. SGTIN-96 的 96 位结构、UPC 到 GTIN-14 的扩展与分区规则
做转换之前,先把 SGTIN-96 的比特布局记牢。96 位里没有任何一位是多余的,少移位一位,读写器就会把标签当成未知格式。
2.1 96 位切分:Header/F 值/Partition/CP/ItemRef/序列号
SGTIN-96 从前到后依次是 8 位 Header、3 位 Filter、3 位 Partition、20 到 40 位的公司前缀(Company Prefix)、4 到 24 位的项目引用(Item Reference)、38 位序列号。Header 固定为0x30,表示这 96 位是 SGTIN-96,而不是 SGTIN-198 或 SSCC-96。后三段加起来是 44 位,用来表示 13 位十进制基础数据;最后的 38 位序列号最多能到2^38 - 1,也就是 274877906943。
Filter 值不是业务属性,它的作用是让读写器在密集 RFID 环境里快速过滤标签类型。比如货架上混着零售单件和组合包装,读写器可以只读 Filter=1 的标签。Partition 决定公司前缀和项目引用各占多少位,两者总和固定在 44 位。公司前缀越长,项目引用就越短,编码的数字位数也随之变化。UPC-A 转换时,通常落到 Partition 6,也就是公司前缀 20 位、项目引用 24 位,因为 UPC-A 的 6 位“系统字符+厂商代码”正好对应 6 位公司前缀。
2.2 UPC-A 校验位与 GTIN-14 的展开公式
UPC-A 的 12 位数字中,最后 1 位是校验位,真正参与 SGTIN 编码的是前 11 位。SGTIN-96 并不直接写 GTIN-14 的校验位,但编码器必须校验 UPC 原始校验位,否则后续反解时导出的 GTIN 会不一致。GTIN-14 校验位的算法是:取 13 位基础数字,从右往左交替乘 3 和 1,累加后取 10 的补数。
UPC-A 转 GTIN-14 时,常见做法是把 UPC-A 去掉校验位后的 11 位数字拆成两段:前 6 位作为公司前缀,后 5 位作为项目引用,再把项目引用左侧补零到 7 位,拼成 13 位基础数字,最后重新计算第 14 位校验位。例如012345678905,去掉校验位得到01234567890,公司前缀012345,项目引用67890补零成0067890,基础数字是0123450067890,重算校验位后 GTIN-14 为01234500678905。注意补零的位置在项目引用左侧,不是右侧。
求 GTIN 校验位的函数可以直接写成下面的 Python 代码:
def gs1_check_digit(basic: str) -> str: if not basic.isdigit(): raise ValueError("基础数字必须为纯数字") total = 0 for idx, ch in enumerate(reversed(basic)): total += int(ch) * (3 if idx % 2 == 0 else 1) return str((10 - total % 10) % 10)代码从字符串最右边开始遍历,所以idx=0时对应最右侧一位,权重是 3。GTIN-14、UPC-A、EAN-13 的校验位计算都遵守这个规律,只是位长不同。不要从左往右套公式,否则在边界数字上会算错。
2.3 分区值与公司前缀长度对照表
分区选择的依据是“公司前缀数字长度”和“项目引用数字长度”,两者相加必须等于 13。这个 13 对应的是 GTIN-14 中除掉校验位后的 13 位基础数字。GS1 对此有官方分区表,编码器在拼位前必须查表。
| Partition | 公司前缀位数 | 项目引用位数 | 公司前缀十进制位数 | 项目引用十进制位数 |
|---|---|---|---|---|
| 0 | 40 | 4 | 12 | 1 |
| 1 | 37 | 7 | 11 | 2 |
| 2 | 34 | 10 | 10 | 3 |
| 3 | 30 | 14 | 9 | 4 |
| 4 | 27 | 17 | 8 | 5 |
| 5 | 24 | 20 | 7 | 6 |
| 6 | 20 | 24 | 6 | 7 |
以分区 6 为例,公司前缀只能写 6 位十进制数,项目引用只写 7 位十进制数,所以项目引用位数是 7。UPC-A 的产品代码只有 5 位,不补零就会少两个字符,最终位串会短一截。GS1 公司前缀实际长度不一定等于表里这个数,因为表里的公司前缀字段已经包含了 GTIN 最左边的包装指示符/系统字符。对 UPC-A 来说,第一位系统字符“0”占用这个位置,因此 5 位厂商码加上这个“0”正好组成 6 位。
3. Python 实现:把 UPC-A 编码成 SGTIN-96 EPC
编码过程不复杂,但位运算一旦写错,结果会偏差很远。我一般把整个函数拆成三步:先算校验位,再构造 GTIN-14 基础数字,最后按分区位移入 96 位整数。
3.1 先写校验位函数
校验位函数在上一章已经给出,这里直接复用。为了工程上更严谨,函数里会先检查输入是不是数字,防止 CSV 里混入空格或字母。用reversed而不是倒序切片,是因为对字符串和列表都通用,逻辑也更直观。
3.2 UPC 转 SGTIN-96 的编码函数
下面的函数接受 UPC-A 字符串、序列号、Filter 值,返回 GTIN-14 和 24 位十六进制 EPC。为了聚焦核心逻辑,这里只覆盖最常见的分区 6,也就是公司前缀为 6 位的情况。
from typing import Tuple # 分区表: {分区: (公司前缀位数, 公司前缀十进制位数, # 项目引用位数, 项目引用十进制位数)} PARTITION_TABLE = { 0: (40, 12, 4, 1), 1: (37, 11, 7, 2), 2: (34, 10, 10, 3), 3: (30, 9, 14, 4), 4: (27, 8, 17, 5), 5: (24, 7, 20, 6), 6: (20, 6, 24, 7), } def upc_to_sgtin96(upc: str, serial: int = 1, filter_value: int = 1) -> Tuple[str, str]: upc = upc.strip() if len(upc) == 11: # 用户可能已经去掉了UPC校验位 upc = upc + gs1_check_digit(upc) if len(upc) != 12 or not upc.isdigit(): raise ValueError("UPC-A 必须是12位纯数字") if upc[-1] != gs1_check_digit(upc[:11]): raise ValueError("UPC-A 校验位错误") # UPC-A 去校验位,取前6位作为公司前缀 data = upc[:11] company_prefix = data[:6] item_ref = data[6:].zfill(7) # 5位产品码左侧补零到7位 # 构造 GTIN-14 基础数字,并计算校验位 basic13 = company_prefix + item_ref gtin14 = basic13 + gs1_check_digit(basic13) partition = 6 cp_bits, cp_digits, item_bits, item_digits = PARTITION_TABLE[partition] cp_val = int(company_prefix) item_val = int(item_ref) # 按位拼装: Header + Filter + Partition + CP + ItemRef + Serial epc_val = 0x30 epc_val = (epc_val << 3) | (filter_value & 0x07) epc_val = (epc_val << 3) | partition epc_val = (epc_val << cp_bits) | cp_val epc_val = (epc_val << item_bits) | item_val if serial < 0 or serial > 0x3FFFFFFFFF: raise ValueError("序列号必须在 0 到 2^38-1 之间") epc_val = (epc_val << 38) | serial epc_hex = f"{epc_val:024X}" return gtin14, epc_hex位运算顺序是整段代码的核心。Header 占 8 位,所以先左移 3 位放入 Filter,再左移 3 位放入 Partition;这之后已经用了 14 位。接着左移cp_bits位放入公司前缀,再左移item_bits位放入项目引用,最后左移 38 位放入序列号。serial & 0x3FFFFFFFFF这种写法会静默截断溢出,所以这里改成显式判断,越界直接抛异常,避免生成一个看起来合法但序列号错误的 EPC。
3.3 用真实 UPC-A 走一遍编码过程
以012345678905、序列号1、Filter1为例,函数返回值如下:
GTIN-14: 01234500678905 EPC(SGTIN-96): 30380C0E40424C800000000130380C0E40424C8000000001正好 24 个十六进制字符,对应 96 位。开头的30是 SGTIN-96 的 Header,第二个3的高 3 位是 Filter=1,低 3 位与后面的 bit 拼成分区 6。最后 38 位是序列号 1,所以在十六进制尾部体现为0000000001。拿到这个字符串后,可以直接用于 RFID 打印机指令、读写器写入命令或 EPC 标签数据管理系统的导入。
4. 批量 UPC 转 EPC 命令行脚本:参数设计、Filter 选择与常见坑
实际项目里不会只转一个 UPC,通常是几千行的商品主数据。把转换逻辑封装成命令行脚本,输入 CSV,输出 GTIN-14、序列号和 EPC,才能接进现有流程。
4.1 CSV 批量脚本
python upc_to_epc.py items.csv --filter 1 --serial-start 1000对应的upc_to_epc.py核心部分如下:
import argparse import csv def main(): parser = argparse.ArgumentParser() parser.add_argument("csv_file") parser.add_argument("--filter", type=int, default=1) parser.add_argument("--serial-start", type=int, default=1) args = parser.parse_args() with open(args.csv_file, newline="", encoding="utf-8") as f: reader = csv.DictReader(f) for line_no, row in enumerate(reader, start=2): upc = row["upc"].strip() serial = args.serial_start + int(row.get("seq", "0")) try: gtin14, epc = upc_to_sgtin96(upc, serial, args.filter) print(f"{upc},{gtin14},{serial},{epc}") except ValueError as exc: print(f"第{line_no}行错误: {exc}", file=sys.stderr)这里的seq列表示同一 UPC 的第几件商品,实际序列号可能是流水号,也可能是日期+门店编号。序列号的设计应遵循企业内部规则,但不要出现字母,SGTIN-96 的 38 位序列号只接受非负整数。CSV 脚本把错误输出到 stderr,成功结果写到 stdout,便于后续用管道接进数据库或上传工具。
4.2 3 位 Filter 值怎么选
Filter 值直接影响读写器盘点时能否快速筛掉无关标签。选错不会导致 EPC 无法解析,但会造成漏读或重复盘点。
| 值 | 典型对象 |
|---|---|
| 0 | 未知或不适用 |
| 1 | 零售单件,例如超市卖的一瓶洗发水 |
| 2 | 非零售单件,例如整箱里的单独电器 |
| 3 | 零售组合包装,例如 6 瓶装饮料 |
| 4 | 非零售组合包装,例如托盘上的箱子 |
| 5 | 零售构件,例如设备中的一个模块 |
| 6 | 非零售构件 |
| 7 | 保留,不推荐使用 |
与 EAN/UPC 对应的普通零售商品,最常用 Filter=1。如果同一个 EPC 号码同时出现在门店和仓库,建议让后台系统按读写器部署位置区分,而不是靠 Filter 值硬顶。
4.3 分区、校验位、序列号三个高频坑
分区错误是最隐蔽的问题。假设把 UPC-A 的 12 位全部当成公司前缀,或者把项目引用补零放在右侧,编码结果在表面上看还是 24 位十六进制,但用 RFID 读写器反解时,GTIN 会变成另一串数字,导致仓库主数据查不到。项目引用补零必须放在左侧,分区 6 下 5 位产品码要写成 7 位。
校验位是第二常见问题。UPC-A 的校验位不能直接搬进 GTIN-14。GTIN-14 的 13 位基础数字重排了结构,原来的校验位位置被项目引用占掉,必须用基础数字重新计算。常见误用是把 UPC 最后一位作为 GTIN-14 校验位保留,这样生成的 EPC 遇到严格校验的解析器会报校验错。
序列号溢出排第三。38 位能容纳到 274877906943,看似很大,但如果有门店代码 9999 + 日期 9999 + 流水号 99999,强行压缩成 38 位就会出错。脚本里已经做了越界检查,生产环境还要在生成序列号前先算最大容量,避免写入一半后中断。
5. 验证 SGTIN-96 结果:反解十六进制 EPC 回到 UPC-A
拿到 EPC 字符串后,不能只看长度是 24 位就认为没问题。最可靠的验证方式是把 EPC 反解回 GTIN-14,再还原成 UPC-A 比对原始输入。
5.1 反解函数
反解是编码的逆过程:先读 Header 确认是0x30,再读 Filter 和 Partition,根据分区表取出公司前缀和项目引用,最后拼出 GTIN-14 并校验。
def sgtin96_to_gtin(epc_hex: str) -> Tuple[int, int, str, str, str]: epc_val = int(epc_hex, 16) header = (epc_val >> 88) & 0xFF if header != 0x30: raise ValueError("Header 不是 0x30,不是 SGTIN-96") filter_value = (epc_val >> 85) & 0x07 partition = (epc_val >> 82) & 0x07 cp_bits, cp_digits, item_bits, item_digits = PARTITION_TABLE[partition] cp_val = (epc_val >> (38 + item_bits)) & ((1 << cp_bits) - 1) item_val = (epc_val >> 38) & ((1 << item_bits) - 1) company_prefix = str(cp_val).zfill(cp_digits) item_ref = str(item_val).zfill(item_digits) basic13 = company_prefix + item_ref gtin14 = basic13 + gs1_check_digit(basic13) return filter_value, partition, company_prefix, item_ref, gtin14针对分区 6 的存货,可以再把 GTIN-14 还原成 UPC-A:取公司前缀 6 位,拼接项目引用的最后 5 位,重新计算 UPC-A 校验位。
def gtin14_to_upc(gtin14: str, partition: int = 6) -> str: if gtin14[-1] != gs1_check_digit(gtin14[:13]): raise ValueError("GTIN-14 校验位错误") basic13 = gtin14[:13] company_prefix = basic13[:6] product_code = basic13[-5:] upc_data = company_prefix + product_code return upc_data + gs1_check_digit(upc_data)用gtin14_to_upc("01234500678905", 6)反推,得到012345678905,与最初输入一致。这里默认分区 6,因为 UCP-A 转换落地几乎都用 6;其他分区需要按分区表重新计算前缀和后缀长度,不要硬套。
5.2 手工检查 96 位二进制
如果现场没有 Python,可以用任何支持位运算的语言把 EPC 转成二进制人工核对。比如30380C0E40424C8000000001开头 14 位是00110000001110,前 8 位00110000是 0x30,随后 3 位001是 Filter=1,再后 3 位110是分区 6。检查完这几项,就能确认标签没有被误写成 SGTIN-198 或 SSCC-96。
对线上系统还有一种更简单的校验:把 EPC 十六进制字符串交给 RFID 读写器读取后,再让后台从 EPC 中解析 GTIN,看是否落在商品主数据表里。特地说一句话,很多打印机指令要求 EPC 连续大写且不带空格,例如30380C0E40424C8000000001直接跟在?或>指令后面;中间插入空格会让读写器按错误长度截断。
本文还有配套的精品资源,点击获取