IXNORFID Insight
← 返回洞察
工程实战2026-09-22 · 12 min

KLMB100/200 开发指南:从 SDK 到生产代码

翻遍 SDK V4.0 文档,拆解 KLMB100/200 的协议结构、API 调用、缓冲区解析。不是官网参数表,是真实能跑的代码。

前七篇文章里,我们一直在用 KL9700 当主角。但很多客户实际拿到的是 KLMB100 或 KLMB200——更小巧、更便宜、直接嵌入产线的那种。

问题是:这俩玩意儿的 SDK 长什么样?怎么写代码才能稳定跑?

今天这篇文章,我把 SDK V4.0 翻了一遍,把协议结构、API 调用、缓冲区解析全拆给你看。不是官网参数表,是真实能跑的代码。

一、硬件全景:KLMB100 vs KLMB200

先说结论:KLMB100 和 KLMB200 用的是同一套 SDK,同一套协议,同一组 DLL。区别只在物理形态和天线配置。

<pre><code class="lang-yaml"># KLMB100/KLMB200 硬件规格(来自 SDK 文档) hardware: klmb100: form_factor: "小型嵌入式模块" antenna_ports: 1 rf_power_max: "26 dBm" interfaces: - "TCP/IP (RJ45)" - "USB (HID)" - "RS232" use_cases: - "产线单点采集" - "工位打卡" - "小型设备集成" klmb200: form_factor: "工业级读写器" antenna_ports: 2 rf_power_max: "26 dBm" interfaces: - "TCP/IP (RJ45)" - "USB (HID)" - "RS232/RS485" - "WIFI (可选)" - "4G (可选)" use_cases: - "多天线覆盖" - "仓储出入口" - "远距离识别" common: protocol: "SWNetApi / SWComApi / SWHidApi" buffer_size: 9182 default_ip: "192.168.1.250" default_port: 60000 tag_types: - "EPC (0x01)" - "TID (0x02)" frequency_bands: - "US (920-925 MHz)" - "EU (865-868 MHz)" - "CN (920-925 MHz)"</code></pre>

二、三种连接方式:TCP / COM / USB

SDK 提供了三套 DLL,对应三种物理接口。但它们的 API 命名几乎一模一样——只是前缀不同。

<pre><code class="lang-python"># 三种连接方式的 DLL 和 API 对照 """ TCP (网线/WIFI/4G): SWNetApi.dll → SWNet_OpenDevice COM (串口): SWComApi.dll → SWCom_OpenDevice USB (HID): SWHidApi.dll → SWHid_OpenDevice """ import ctypes from ctypes import byref, c_int # === 方式一:TCP 连接(最常用)=== def connect_tcp(ip="192.168.1.250", port=60000): dll = ctypes.windll.LoadLibrary("SWNetApi.dll") ret = dll.SWNet_OpenDevice(ip.encode(), port) if ret == 1: print("TCP 连接成功") return dll else: print("TCP 连接失败") return None # === 方式二:COM 串口 === def connect_com(com_port="COM4", baudrate=115200): dll = ctypes.windll.LoadLibrary("SWComApi.dll") ret = dll.SWCom_OpenDevice(com_port.encode(), baudrate) if ret == 1: print("COM 连接成功") return dll else: print("COM 连接失败") return None # === 方式三:USB HID === def connect_usb(device_index=0): dll = ctypes.windll.LoadLibrary("SWHidApi.dll") # 先检测 USB 设备数量 count = dll.SWHid_GetUsbCount() if count == 0: print("未检测到 USB 设备") return None ret = dll.SWHid_OpenDevice(device_index) if ret == 1: print("USB 连接成功") return dll else: print("USB 连接失败") return None</code></pre>

三、缓冲区解析:9182 字节里的秘密

不管用哪种连接方式,读到的数据都是同一个格式:一个 9182 字节的缓冲区,里面塞着若干条标签记录。

每条记录的结构是这样的:

<pre><code># 缓冲区结构(每条标签记录) ┌─────────────┬──────────┬─────────┬──────────────┬──────────┐ │ PackLength │ Type │ Ant │ TagID... │ RSSI │ │ (1 byte) │ (1 byte) │(1 byte)│ (N bytes) │ (1 byte) │ └─────────────┴──────────┴─────────┴──────────────┴──────────┘ 字段说明: - PackLength: 本条记录的总长度(不含自身) - Type: 标签类型(0x01=EPC, 0x02=TID, 0x81=EPC+时间戳) - Ant: 天线端口号(1-4) - TagID: 标签内容(EPC 或 TID,长度可变) - RSSI: 信号强度(负值,需转换)</code></pre>

<pre><code class="lang-python"># 完整的缓冲区解析代码 import ctypes from ctypes import byref, c_int import time def read_tags(dll, duration=10): """ 持续读取标签,直到超时 Args: dll: 已加载的 DLL 对象 duration: 读取时长(秒) """ # 清空缓冲区 dll.SWNet_ClearTagBuf() start_time = time.time() tag_count = 0 while time.time() - start_time &lt; duration: # 分配 9182 字节缓冲区 arrBuffer = bytes(9182) iTagLength = c_int(0) iTagNumber = c_int(0) # 读取缓冲区 ret = dll.SWNet_GetTagBuf(arrBuffer, byref(iTagLength), byref(iTagNumber)) if iTagNumber.value &gt; 0: iLength = 0 # 遍历每条标签记录 for i in range(iTagNumber.value): # 读取 PackLength bPackLength = arrBuffer[iLength] # 读取 Type 和 Ant tag_type = arrBuffer[1 + iLength] ant_num = arrBuffer[1 + iLength + 1] # 读取 TagID(可变长度) tag_id_bytes = [] for j in range(2, bPackLength - 1): tag_id_bytes.append(arrBuffer[1 + iLength + j]) tag_id = ''.join(f'{b:02X}' for b in tag_id_bytes) # 读取 RSSI rssi_raw = arrBuffer[1 + iLength + bPackLength - 1] rssi = rssi_raw - 256 if rssi_raw &gt; 127 else rssi_raw # 输出 type_str = "EPC" if tag_type == 0x01 else "TID" if tag_type == 0x02 else f"0x{tag_type:02X}" print(f"[{type_str}] Ant:{ant_num} Tag:{tag_id} RSSI:{rssi}dBm") tag_count += 1 # 移动到下条记录 iLength = iLength + bPackLength + 1 time.sleep(0.1) # 避免 CPU 空转 print(f"\n总计读取 {tag_count} 条标签") return tag_count</code></pre>

四、主动模式 vs 应答模式

KLMB100/200 有两种工作模式,决定了谁来控制"读"这个动作。

<pre><code class="lang-yaml"># 两种工作模式对比 work_modes: active_mode: description: "上电后自动连续读标签,数据主动推送到指定接口" trigger: "设备自动" data_flow: "设备 → 主机(推送)" use_cases: - "产线持续采集" - "仓储出入口监控" - "实时盘点" config: "ReaderSoft → ParameterSet → WorkMode → ActiveMode" answer_mode: description: "上电后不读标签,等待主机发送读命令,每命令读一次" trigger: "主机命令" data_flow: "主机请求 → 设备响应" use_cases: - "按需读取" - "功耗敏感场景" - "精确控制采集时机" config: "ReaderSoft → ParameterSet → WorkMode → AnswerMode" # TCP 命令协议(直接发指令控制) tcp_commands: header: [0x53, 0x57] # "SW" format: "Header + Length + CMD + Data + Checksum" examples: set_active: [0x53, 0x57, 0x00, 0x05, 0xFF, 0x24, 0x02, 0x01, 0x2B] set_answer: [0x53, 0x57, 0x00, 0x05, 0xFF, 0x24, 0x02, 0x00, 0x2C] set_power_26dbm: [0x53, 0x57, 0x00, 0x05, 0xFF, 0x24, 0x05, 0x1A, 0x24] set_power_7dbm: [0x53, 0x57, 0x00, 0x05, 0xFF, 0x24, 0x05, 0x07, 0x22] set_freq_us: [0x53, 0x57, 0x00, 0x05, 0xFF, 0x3F, 0x31, 0x80, 0x62] set_freq_eu: [0x53, 0x57, 0x00, 0x05, 0xFF, 0x3F, 0x4E, 0x00, 0xC5]</code></pre>

五、高级功能:掩码、继电器、时间戳

除了基本的读标签,KLMB100/200 还支持一些实用功能。

<pre><code class="lang-python"># 高级功能配置 """ 1. 掩码过滤:只读取特定前缀的标签 2. 继电器控制:读到标签后触发外部设备 3. 时间戳:每条标签附带读取时间 """ # === 掩码过滤 === """ 配置方式:ReaderSoft → AdvanceSet → Mask - 开启 Mask 功能 - Start(Hex): 起始地址(通常设为 0) - MaskData(Hex): 要匹配的前缀 示例:只读取 1122 开头的标签 Start = 0 MaskData = 1122 效果: ✓ 112233445566778899AABB (匹配) ✗ 2233445566778899AABB00 (不匹配) """ # === 继电器控制 === """ 硬件接线: COM → 公共端 KOFF → 默认断开(继电器释放时连通) KON → 默认连通(继电器释放时断开) 自动模式: ReaderSoft → AdvanceSet → Relay - 开启 Relay - ValidTime = 3(秒) 效果:每次读到标签,继电器闭合 3 秒后自动断开 SDK 控制: dll.SWNet_RelayOn() # 闭合继电器 dll.SWNet_RelayOff() # 释放继电器 """ # === 时间戳模式 === """ 当 Type = 0x81 时,TagID 后面多 6 字节时间戳 缓冲区结构变化: 正常: [PackLen][Type=0x01][Ant][TagID...][RSSI] 时间戳: [PackLen][Type=0x81][Ant][TagID...][Timestamp 6B][RSSI] 时间戳格式:Unix 时间戳(秒) """ def parse_tag_with_timestamp(arrBuffer, iLength, bPackLength): """解析带时间戳的标签""" tag_type = arrBuffer[1 + iLength] if tag_type == 0x81: # 带时间戳 # TagID 长度 = bPackLength - 1(Type) - 1(Ant) - 6(Timestamp) - 1(RSSI) tag_len = bPackLength - 9 tag_id = ''.join(f'{arrBuffer[1 + iLength + 2 + j]:02X}' for j in range(tag_len)) # 时间戳(最后 6 字节,跳过 RSSI) ts_bytes = arrBuffer[1 + iLength + 2 + tag_len : 1 + iLength + 2 + tag_len + 6] timestamp = int.from_bytes(ts_bytes, 'big') rssi = arrBuffer[1 + iLength + bPackLength - 1] rssi = rssi - 256 if rssi &gt; 127 else rssi return { 'type': 'EPC+TS', 'tag_id': tag_id, 'timestamp': timestamp, 'rssi': rssi } return None # 非时间戳模式</code></pre>

六、生产环境代码:断线重连 + 异常处理

SDK 文档里提到一个关键特性:TCP 断线后自动重连。但代码层面还是要做好异常处理。

<pre><code class="lang-python"># 生产级 KLMB100/200 客户端 import ctypes from ctypes import byref, c_int import time import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class KLMReader: """KLMB100/200 读写器客户端""" def __init__(self, ip="192.168.1.250", port=60000): self.ip = ip self.port = port self.dll = None self.connected = False def connect(self): """建立连接""" try: self.dll = ctypes.windll.LoadLibrary("SWNetApi.dll") ret = self.dll.SWNet_OpenDevice(self.ip.encode(), self.port) if ret == 1: self.connected = True logger.info(f"已连接到 {self.ip}:{self.port}") # 清空缓冲区 self.dll.SWNet_ClearTagBuf() return True else: logger.error("连接失败") return False except Exception as e: logger.error(f"连接异常: {e}") return False def read_loop(self, callback, duration=None): """ 持续读取标签 Args: callback: 每读到标签时的回调函数 callback(tag_data) duration: 读取时长(秒),None 表示无限 """ if not self.connected: raise RuntimeError("未连接") start_time = time.time() while True: # 检查超时 if duration and (time.time() - start_time) &gt; duration: break try: arrBuffer = bytes(9182) iTagLength = c_int(0) iTagNumber = c_int(0) ret = self.dll.SWNet_GetTagBuf( arrBuffer, byref(iTagLength), byref(iTagNumber) ) if iTagNumber.value &gt; 0: iLength = 0 for i in range(iTagNumber.value): bPackLength = arrBuffer[iLength] # 解析字段 tag_type = arrBuffer[1 + iLength] ant_num = arrBuffer[1 + iLength + 1] # TagID tag_id_bytes = [] for j in range(2, bPackLength - 1): tag_id_bytes.append(arrBuffer[1 + iLength + j]) tag_id = ''.join(f'{b:02X}' for b in tag_id_bytes) # RSSI rssi_raw = arrBuffer[1 + iLength + bPackLength - 1] rssi = rssi_raw - 256 if rssi_raw &gt; 127 else rssi_raw # 构造数据 tag_data = { 'type': tag_type, 'antenna': ant_num, 'epc': tag_id, 'rssi': rssi, 'timestamp': time.time() } # 回调 callback(tag_data) # 移动到下条 iLength = iLength + bPackLength + 1 time.sleep(0.05) # 50ms 轮询间隔 except Exception as e: logger.error(f"读取异常: {e}") # 尝试重连 self.connected = False if not self._try_reconnect(): time.sleep(5) # 重连失败,等待 5 秒 return True def _try_reconnect(self, max_retries=3): """尝试重连""" for i in range(max_retries): logger.info(f"尝试重连 ({i+1}/{max_retries})...") if self.connect(): return True time.sleep(2) return False def close(self): """关闭连接""" if self.dll: try: self.dll.SWNet_CloseDevice() except: pass self.connected = False logger.info("已断开连接") # === 使用示例 === def on_tag_detected(tag): """标签检测回调""" print(f"[Ant{tag['antenna']}] {tag['epc']} ({tag['rssi']}dBm)") if __name__ == "__main__": reader = KLMReader("192.168.1.250", 60000) if reader.connect(): try: # 持续读取 60 秒 reader.read_loop(on_tag_detected, duration=60) except KeyboardInterrupt: logger.info("用户中断") finally: reader.close()</code></pre>

七、KLMB100/200 vs KL9700:怎么选?

最后做个对比,帮你选型。

<pre><code class="lang-yaml"># KLMB100/200 vs KL9700 选型指南 comparison: klmb100: pros: - "体积小,易嵌入" - "价格低" - "SDK 简单,上手快" cons: - "单天线,覆盖有限" - "无 GPIO" - "无 MQTT/HTTP" best_for: - "产线单点采集" - "设备集成" - "成本敏感项目" klmb200: pros: - "双天线,覆盖更广" - "支持 WIFI/4G" - "继电器输出" cons: - "体积较大" - "价格中等" best_for: - "仓储出入口" - "多点位采集" - "需要无线联网" kl9700: pros: - "四天线,覆盖最大" - "GPIO 丰富" - "MQTT/HTTP/Modbus" - "工业级防护" cons: - "体积最大" - "价格最高" best_for: - "大型仓储" - "复杂工业环境" - "需要协议对接" sdk_compatibility: note: "三者 SDK 完全兼容" shared_features: - "相同的缓冲区结构" - "相同的 API 命名" - "相同的协议格式" migration: "代码从 KLMB100 迁移到 KL9700,只需改 DLL 路径"</code></pre>

八、结语

KLMB100/200 的 SDK 设计得很朴素——三种接口、一套协议、一个缓冲区。没有花哨的功能,但足够稳定。

如果你要做产线集成,KLMB100 足够;如果要覆盖更大区域,KLMB200 的双天线更合适;如果需要 MQTT 或更多 GPIO,上 KL9700。

代码层面,记住三点:9182 字节缓冲区、PackLength 变长解析、断线自动重连。做到这三点,就能稳定跑起来。

下一篇文章,我们讲讲怎么用 KLMB100/200 搭建一个低成本的多点位采集系统。

继续阅读