<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>北京泰诺恒创科技 on TiJet</title><link>https://www.tainuohc.com/</link><description>Recent content in 北京泰诺恒创科技 on TiJet</description><generator>Hugo</generator><language>zh</language><lastBuildDate>Tue, 04 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.tainuohc.com/index.xml" rel="self" type="application/rss+xml"/><item><title>当前开启打印流程</title><link>https://www.tainuohc.com/zh/docs/sdk/quickstart/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/zh/docs/sdk/quickstart/</guid><description>&lt;h1 id="当前开启打印流程">当前开启打印流程&lt;/h1>
&lt;p>本文档记录当前 C 客户端、C SDK 和 &lt;code>tijet_1/tincore&lt;/code> gRPC 服务端的实际开启打印流程。&lt;/p>
&lt;p>本文档只描述当前代码和当前客户端行为，不包含未来改造方案。&lt;/p>
&lt;h2 id="源码基准">源码基准&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>模块&lt;/th>
 &lt;th>代码库&lt;/th>
 &lt;th>分支&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Proto 契约与 C SDK&lt;/td>
 &lt;td>&lt;code>tijet_1/sw-grpc-libraries&lt;/code>&lt;/td>
 &lt;td>&lt;code>master&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>tincore gRPC 服务端&lt;/td>
 &lt;td>&lt;code>tijet_1/tincore&lt;/code>&lt;/td>
 &lt;td>&lt;code>feature-grpc-cpp&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="参与者">参与者&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>参与者&lt;/th>
 &lt;th>职责&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Qt 客户端业务层&lt;/td>
 &lt;td>注册事件回调、上传打印数据、重试 &lt;code>START_PRINT&lt;/code>、等待最终结果&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>C SDK 调用线程&lt;/td>
 &lt;td>封装 &lt;code>SetPrintData&lt;/code> 和 &lt;code>StartPrint&lt;/code> RPC&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>C SDK 事件线程&lt;/td>
 &lt;td>持续读取 &lt;code>SubscribeEvents&lt;/code> 服务端流，并调用客户注册的事件回调&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>TinCoreServiceImpl&lt;/code>&lt;/td>
 &lt;td>gRPC 协议层，将 RPC 请求转交给服务端 Handler&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>DefaultTinCoreHandlers&lt;/code>&lt;/td>
 &lt;td>执行打印数据接收、数据就绪校验和异步开启打印逻辑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>gAFBridge&lt;/code> / 喷头硬件&lt;/td>
 &lt;td>后台处理打印数据、控制驱动板电源、与喷头通信并开启高压&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="当前调用时序">当前调用时序&lt;/h2>
&lt;pre class="mermaid">sequenceDiagram
 autonumber
 participant Caller as 调用端
 participant Callee as 被调用端

 Caller-&amp;gt;&amp;gt;Callee: tijet_client_set_event_callback(callback, user_data)
 Callee--&amp;gt;&amp;gt;Caller: 回调注册完成

 Caller-&amp;gt;&amp;gt;Callee: tijet_client_start_event_subscription()
 Callee--&amp;gt;&amp;gt;Caller: TIJET_OK
 Note over Callee: 后台创建事件线程
 Note over Callee: SubscribeEvents() 持续读取

 Caller-&amp;gt;&amp;gt;Callee: tijet_client_set_print_data(data, index, is_static)
 Callee--&amp;gt;&amp;gt;Caller: SetPrintDataResponse{success=true}
 Note over Callee: 打印数据进入后台处理
 Note over Callee: 返回时 DATA_READY 可能仍为 0

 loop START_PRINT 重试，总超时 10 秒
 Caller-&amp;gt;&amp;gt;Callee: tijet_client_print_control(START_PRINT)
 alt DATA_READY == 0
 Callee--&amp;gt;&amp;gt;Caller: TIJET_ERR_SERVER
 Note over Caller: 等待 1 秒后重试
 else DATA_READY != 0
 Callee--&amp;gt;&amp;gt;Caller: TIJET_OK
 Note over Callee: 创建高压启动线程
 end
 end

 Note over Caller: START_PRINT 成功后，等待 PrintStarted

 alt 高压开启成功
 Callee--&amp;gt;&amp;gt;Caller: PrintStarted{success=true}
 Note over Caller: 可进入喷印状态
 else 高压开启失败
 Callee--&amp;gt;&amp;gt;Caller: PrintStarted{success=false}
 Note over Caller: 失败，message 含未就绪喷头数
 else 10 秒未收到 PrintStarted
 Note over Caller: 等待超时
 end&lt;/pre>
&lt;h2 id="重试与超时流程">重试与超时流程&lt;/h2>
&lt;pre class="mermaid">flowchart TD
 A[&amp;#34;调用端: 连接服务端&amp;#34;] --&amp;gt; B[&amp;#34;调用端: 注册事件回调&amp;lt;br/&amp;gt;启动事件订阅&amp;#34;]
 B --&amp;gt; C[&amp;#34;调用端: set_print_data() 上传打印数据&amp;#34;]
 C --&amp;gt; D{&amp;#34;被调用端: 上传 RPC 成功？&amp;#34;}

 D --&amp;gt;|&amp;#34;否&amp;#34;| D1[&amp;#34;失败，流程结束&amp;#34;]
 D --&amp;gt;|&amp;#34;是&amp;#34;| E[&amp;#34;被调用端: 数据进入后台处理&amp;lt;br/&amp;gt;DATA_READY 可能仍为 0&amp;#34;]

 E --&amp;gt; F[&amp;#34;调用端: 开始 START_PRINT 重试计时&amp;#34;]
 F --&amp;gt; G[&amp;#34;调用端: print_control(START_PRINT)&amp;#34;]
 G --&amp;gt; H{&amp;#34;被调用端: DATA_READY 是否为 0？&amp;#34;}

 H --&amp;gt;|&amp;#34;是：数据未就绪&amp;#34;| I{&amp;#34;重试总时间达 10 秒？&amp;#34;}
 I --&amp;gt;|&amp;#34;否&amp;#34;| J[&amp;#34;调用端: 等待 1 秒&amp;#34;]
 J --&amp;gt; G
 I --&amp;gt;|&amp;#34;是&amp;#34;| K[&amp;#34;失败：打印数据准备超时&amp;#34;]

 H --&amp;gt;|&amp;#34;否：数据已就绪&amp;#34;| L[&amp;#34;被调用端: 创建高压启动线程&amp;lt;br/&amp;gt;返回 TIJET_OK&amp;#34;]
 L --&amp;gt; M[&amp;#34;调用端: 等待 PrintStarted 事件&amp;lt;br/&amp;gt;超时 10 秒&amp;#34;]
 M --&amp;gt; N{&amp;#34;结果&amp;#34;}

 N --&amp;gt;|&amp;#34;success=true&amp;#34;| O[&amp;#34;成功：高压开启完成&amp;lt;br/&amp;gt;进入可喷印状态&amp;#34;]
 N --&amp;gt;|&amp;#34;success=false&amp;#34;| P[&amp;#34;失败：高压开启失败&amp;#34;]
 N --&amp;gt;|&amp;#34;超时&amp;#34;| Q[&amp;#34;失败：等待高压结果超时&amp;#34;]&lt;/pre>
&lt;h2 id="返回值的当前含义">返回值的当前含义&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>位置&lt;/th>
 &lt;th>返回值&lt;/th>
 &lt;th>当前实际含义&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>tijet_client_set_print_data()&lt;/code>&lt;/td>
 &lt;td>&lt;code>TIJET_OK&lt;/code>&lt;/td>
 &lt;td>打印数据上传 RPC 已完成；不表示后台数据处理完成，也不表示 &lt;code>DATA_READY=1&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>tijet_client_print_control(..., &amp;quot;START_PRINT&amp;quot;)&lt;/code>&lt;/td>
 &lt;td>&lt;code>TIJET_ERR_SERVER&lt;/code>&lt;/td>
 &lt;td>gRPC 调用失败，或服务端返回 &lt;code>success=false&lt;/code>；当前客户端不区分原因，统一等待 1 秒后重试&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>tijet_client_print_control(..., &amp;quot;START_PRINT&amp;quot;)&lt;/code>&lt;/td>
 &lt;td>&lt;code>TIJET_OK&lt;/code>&lt;/td>
 &lt;td>服务端同步校验通过并创建了高压启动线程；不表示高压已经开启&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>PrintStarted.success&lt;/code>&lt;/td>
 &lt;td>&lt;code>true&lt;/code>&lt;/td>
 &lt;td>高压开启成功，并已调用 &lt;code>setDataParamReady(true)&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>PrintStarted.success&lt;/code>&lt;/td>
 &lt;td>&lt;code>false&lt;/code>&lt;/td>
 &lt;td>高压开启失败，&lt;code>message&lt;/code> 包含未就绪喷头数量&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="源码索引">源码索引&lt;/h2>
&lt;h3 id="c-sdk">C SDK&lt;/h3>
&lt;ul>
&lt;li>&lt;code>sdk/c/src/tijet_core.cpp:342&lt;/code>：&lt;code>tijet_client_set_print_data()&lt;/code>。&lt;/li>
&lt;li>&lt;code>sdk/c/src/tijet_core.cpp:396&lt;/code>：注册事件回调。&lt;/li>
&lt;li>&lt;code>sdk/c/src/tijet_core.cpp:408&lt;/code>：启动事件订阅和创建事件线程。&lt;/li>
&lt;li>&lt;code>sdk/c/src/tijet_core.cpp:641&lt;/code>：&lt;code>tijet_client_print_control()&lt;/code>。&lt;/li>
&lt;li>&lt;code>sdk/c/src/tijet_core.cpp:655&lt;/code>：匹配 &lt;code>START_PRINT&lt;/code> 并调用 &lt;code>StartPrint&lt;/code> RPC。&lt;/li>
&lt;li>&lt;code>sdk/c/include/tijet_core.h:441&lt;/code>：打印数据上传 C API 声明。&lt;/li>
&lt;li>&lt;code>sdk/c/include/tijet_core.h:480&lt;/code>：事件回调注册 C API 声明。&lt;/li>
&lt;li>&lt;code>sdk/c/include/tijet_core.h:500&lt;/code>：事件订阅 C API 声明。&lt;/li>
&lt;li>&lt;code>sdk/c/include/tijet_core.h:650&lt;/code>：打印控制 C API 声明。&lt;/li>
&lt;/ul>
&lt;h3 id="proto-契约">Proto 契约&lt;/h3>
&lt;ul>
&lt;li>&lt;code>proto/print.proto:7&lt;/code>：&lt;code>SetPrintDataRequest&lt;/code>。&lt;/li>
&lt;li>&lt;code>proto/print.proto:19&lt;/code>：&lt;code>StartPrintRequest&lt;/code>。&lt;/li>
&lt;li>&lt;code>proto/print.proto:22&lt;/code>：&lt;code>StartPrintResponse&lt;/code>。&lt;/li>
&lt;li>&lt;code>proto/event.proto:48&lt;/code>：&lt;code>PrintDataStatus&lt;/code>。&lt;/li>
&lt;li>&lt;code>proto/event.proto:53&lt;/code>：&lt;code>PrintStarted&lt;/code>。&lt;/li>
&lt;li>&lt;code>proto/tincore.proto:27&lt;/code>：&lt;code>SetPrintData&lt;/code> RPC。&lt;/li>
&lt;li>&lt;code>proto/tincore.proto:28&lt;/code>：&lt;code>StartPrint&lt;/code> RPC。&lt;/li>
&lt;li>&lt;code>proto/tincore.proto:33&lt;/code>：&lt;code>SubscribeEvents&lt;/code> RPC。&lt;/li>
&lt;/ul>
&lt;h3 id="tijet_1tincore-grpc-服务端">&lt;code>tijet_1/tincore&lt;/code> gRPC 服务端&lt;/h3>
&lt;p>以下路径相对于 &lt;code>tijet_1/tincore&lt;/code> 代码库的 &lt;code>feature-grpc-cpp&lt;/code> 分支：&lt;/p></description></item><item><title>Hugo 静态网站图片加速：8.6MB 到 310KB 的实战记录</title><link>https://www.tainuohc.com/blog/2026-08-04-hugo-image-optimization/</link><pubDate>Tue, 04 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/blog/2026-08-04-hugo-image-optimization/</guid><description>&lt;h2 id="1-为什么要做图片加速">1. 为什么要做图片加速&lt;/h2>
&lt;p>静态网站加载卡顿，图片往往是最大元凶：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>原因&lt;/th>
 &lt;th>说明&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>图片体积大&lt;/td>
 &lt;td>一张 2560×1440 PNG 可能有 8-10MB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>格式低效&lt;/td>
 &lt;td>PNG 无损压缩，适合截图但不适合照片&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>没有懒加载&lt;/td>
 &lt;td>页面一次性加载所有图片&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>没有缓存&lt;/td>
 &lt;td>每次访问都重新下载&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>核心思路：&lt;/strong> 让浏览器下载更小的文件。&lt;/p>
&lt;h2 id="2-hugo-图片处理管道原理">2. Hugo 图片处理管道原理&lt;/h2>
&lt;p>Hugo 内置图片处理能力，构建时自动完成：&lt;/p>
&lt;pre tabindex="0">&lt;code>原始图片 (assets/img/wall4.png)
 ↓ Hugo 构建时处理
Resize &amp;#34;1920x webp q75&amp;#34;
 ↓
优化后图片 (public/img/wall4_hu_xxx.webp)
&lt;/code>&lt;/pre>&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>操作&lt;/th>
 &lt;th>语法&lt;/th>
 &lt;th>说明&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>缩放&lt;/td>
 &lt;td>&lt;code>Resize &amp;quot;800x&amp;quot;&lt;/code>&lt;/td>
 &lt;td>限制宽度 800px&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>裁剪&lt;/td>
 &lt;td>&lt;code>Crop &amp;quot;800x600&amp;quot;&lt;/code>&lt;/td>
 &lt;td>裁成指定尺寸&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>填充&lt;/td>
 &lt;td>&lt;code>Fill &amp;quot;800x600&amp;quot;&lt;/code>&lt;/td>
 &lt;td>填充并居中裁剪&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>转格式&lt;/td>
 &lt;td>&lt;code>webp&lt;/code> / &lt;code>jpg&lt;/code> / &lt;code>png&lt;/code>&lt;/td>
 &lt;td>WebP 体积最小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>质量&lt;/td>
 &lt;td>&lt;code>q75&lt;/code>&lt;/td>
 &lt;td>75% 质量（默认 75）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="3-方案一css-背景图优化实测">3. 方案一：CSS 背景图优化（实测）&lt;/h2>
&lt;h3 id="步骤-1图片放到-hugo-资源目录">步骤 1：图片放到 Hugo 资源目录&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>mkdir -p web-site/assets/img
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cp 你的图片.png web-site/assets/img/wall4.png
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="步骤-2创建处理-partial">步骤 2：创建处理 partial&lt;/h3>
&lt;p>新建 &lt;code>layouts/partials/hero-bg.html&lt;/code>：&lt;/p></description></item><item><title>Jenkins CI/CD 入门实战：从零搭建自动构建流水线</title><link>https://www.tainuohc.com/blog/2026-08-04-jenkins-cicd/</link><pubDate>Tue, 04 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/blog/2026-08-04-jenkins-cicd/</guid><description>&lt;h2 id="1-为什么要用-jenkins">1. 为什么要用 Jenkins&lt;/h2>
&lt;p>&lt;strong>Jenkins&lt;/strong> 是开源的持续集成/持续部署（CI/CD）工具。核心作用：代码 push 到 Git 仓库后，自动完成「拉代码 → 编译 → 测试 → 打包 → 部署」等一系列操作。&lt;/p>
&lt;pre tabindex="0">&lt;code>你 push 代码到 Gitee
 ↓
Jenkins 自动拉取最新代码
 ↓
自动构建（编译 / hugo / 打包）
 ↓
自动部署 / 归档产物
&lt;/code>&lt;/pre>&lt;h3 id="核心概念">核心概念&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>概念&lt;/th>
 &lt;th>说明&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Job / Item&lt;/td>
 &lt;td>一个构建任务&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pipeline&lt;/td>
 &lt;td>用 Groovy 脚本定义的构建流水线&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Stage&lt;/td>
 &lt;td>流水线里的一个阶段（拉代码→构建→部署）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Step&lt;/td>
 &lt;td>每个阶段里的具体操作&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Webhook&lt;/td>
 &lt;td>Git 仓库通知 Jenkins &amp;ldquo;有新提交了&amp;rdquo;&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="2-安装与启动">2. 安装与启动&lt;/h2>
&lt;h3 id="前提java">前提：Java&lt;/h3>
&lt;p>Jenkins 是 Java 写的，&lt;strong>必须装 Java 17 或 21&lt;/strong>。太新的版本（如 Java 26）启动会报错：&lt;/p>
&lt;pre tabindex="0">&lt;code>Running with Java 26 ... not yet fully supported. Supported Java versions are: [17, 21]
&lt;/code>&lt;/pre>&lt;p>&lt;strong>解决：&lt;/strong> 装 Java 21，用显式路径启动：&lt;/p></description></item><item><title>工控固件安全实战：从裸奔到硬件信任根的完整链路</title><link>https://www.tainuohc.com/blog/2026-07-22-industrial-security/</link><pubDate>Wed, 22 Jul 2026 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/blog/2026-07-22-industrial-security/</guid><description>&lt;h2 id="1-为什么要搞安全启动">1. 为什么要搞安全启动？&lt;/h2>
&lt;p>在工控领域，固件被篡改不只是数据泄露的问题——它是物理安全威胁。被篡改的固件可能禁用看门狗、改写热关断阈值，或悄无声息地窃取核心运动控制参数。&lt;/p>
&lt;p>然而现实是，大多数工业物联网设备出厂时 JTAG 全开，固件完整性校验仅靠 CRC32。CRC32 能抓比特翻转，但对有动机的攻击者毫无作用。&lt;/p>
&lt;p>2025 年中，我们对泰诺固件供应链做了安全审计，发现多处缺口：OTA 载荷未签名、无版本回滚防护、生产设备调试接口未关闭。这篇文章记录完整修复方案。&lt;/p>
&lt;h2 id="2-信任链设计">2. 信任链设计&lt;/h2>
&lt;p>我们的安全启动实现了三级信任链：&lt;/p>
&lt;pre tabindex="0">&lt;code>ROM Boot Code（不可变，掩膜 ROM 固化）
 ↓ 验证 ECDSA 签名
第一级：Bootloader（公钥哈希烧录在 OTP eFuse 中）
 ↓ 验证 ECDSA 签名
第二级：应用固件
 ↓ 验证 ECDSA 签名
第三级：FPGA 比特流 + 标定数据
&lt;/code>&lt;/pre>&lt;p>每一级的公钥内嵌于上一级，信任根锚定于 i.MX RT1064 的一次性可编程（OTP）eFuse。公钥哈希一旦烧入 OTP，除非物理更换芯片，否则无法修改。&lt;/p>
&lt;p>所有签名使用 &lt;strong>ECDSA with NIST P-256&lt;/strong>。选择 ECDSA 而非 RSA 基于两个考量：签名体积更小（64 字节 vs. 同等安全强度的 256+ 字节），以及在 Cortex-M 核上的验证速度更快（无需硬件加密加速器即可获得可接受的性能）。&lt;/p>
&lt;h2 id="3-ota-防回滚">3. OTA 防回滚&lt;/h2>
&lt;p>仅有签名是不够的。攻击者可以截获一个有效但过时的 OTA 包，将其重放以将设备降级到存在已知漏洞的旧版本。&lt;/p>
&lt;p>我们的防回滚机制简单且硬件支持：&lt;/p>
&lt;ul>
&lt;li>每个固件镜像在其签名元数据中携带一个单调递增的&lt;strong>安全版本号&lt;/strong>。&lt;/li>
&lt;li>当前安全版本号存储在 eFuse 实现的&lt;strong>单调计数器&lt;/strong>中（只能烧 1，不能清 0）。&lt;/li>
&lt;li>Bootloader 拒绝任何安全版本号小于 eFuse 计数值的镜像。&lt;/li>
&lt;li>提升安全版本的 OTA 更新会烧录新的 eFuse 比特——这是一个物理上不可逆的操作，即使全片擦除也无法撤销。&lt;/li>
&lt;/ul>
&lt;h2 id="4-cicd-中的密钥管理">4. CI/CD 中的密钥管理&lt;/h2>
&lt;p>密钥管理是大多数安全启动方案在实际落地中最薄弱的环节。密钥存在开发者的笔记本上，就意味着迟早会泄露。&lt;/p></description></item><item><title>驯服 Linux 抖动：PREEMPT_RT + Xenomai 实现 50µs 硬实时闭环控制</title><link>https://www.tainuohc.com/blog/2026-07-21-linux-rt/</link><pubDate>Tue, 21 Jul 2026 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/blog/2026-07-21-linux-rt/</guid><description>&lt;h2 id="1-挑战在-30-美元的-soc-上跑软-plc">1. 挑战：在 30 美元的 SoC 上跑软 PLC&lt;/h2>
&lt;p>泰诺下一代运动控制器需要在单颗 SoC 上运行软 PLC 运行时，同时对六个伺服轴执行 20kHz 的闭环 PID 控制。目标硬件：售价约 30 美元的全志 A40i（四核 Cortex-A7）。没有 FPGA，没有专用 DSP——只有普通的 ARM 核跑 Linux。&lt;/p>
&lt;p>问题显而易见：标准 Linux 即使配置了 &lt;code>SCHED_FIFO&lt;/code>，在这类硬件上仍会出现 200~500µs 的调度抖动。对于周期仅 50µs 的 20kHz 控制环来说，这完全不可接受。错过截止时间意味着相位误差累积，最终导致机械系统失稳。&lt;/p>
&lt;h2 id="2-preempt_rt-地基">2. PREEMPT_RT 地基&lt;/h2>
&lt;p>第一步很直接：应用 &lt;code>PREEMPT_RT&lt;/code> 补丁集，配置内核 &lt;code>CONFIG_PREEMPT_RT_FULL=y&lt;/code>。这几乎将所有 Spinlock 临界区转换为可抢占的互斥锁，让内核实现近乎完全可抢占。&lt;/p>
&lt;p>关键内核配置：&lt;/p>
&lt;pre tabindex="0">&lt;code>CONFIG_PREEMPT_RT_FULL=y
CONFIG_HZ=1000
CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y
CONFIG_NO_HZ_FULL=y
&lt;/code>&lt;/pre>&lt;p>仅凭 &lt;code>PREEMPT_RT&lt;/code>，最差调度延迟从 ~480µs 降至 ~80µs。有改善，但仍达不到 50µs 的预算。&lt;/p>
&lt;h2 id="3-引入-xenomai-4evl-核">3. 引入 Xenomai 4（EVL 核）&lt;/h2>
&lt;p>Xenomai 4 的 EVL 核与 Linux 并行运行一个独立的实时核心，在中断到达 Linux 内核之前就进行拦截。实时线程由 EVL 核直接调度，完全绕过 Linux 调度器。&lt;/p></description></item><item><title>FPGA 加速视觉检测：从 200ms 到 8µs 的延迟优化之路</title><link>https://www.tainuohc.com/blog/2026-07-20-fpga-acceleration/</link><pubDate>Mon, 20 Jul 2026 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/blog/2026-07-20-fpga-acceleration/</guid><description>&lt;h2 id="1-问题的原点软件视觉检测撞上性能墙">1. 问题的原点：软件视觉检测撞上性能墙&lt;/h2>
&lt;p>在泰诺的 PCB 印刷产线中，锡膏检测（SPI）是核心品控环节。每一块经过产线的 PCB 仅有不到 50ms 的时间窗口用于缺陷检测——超出这个时间，整条产线就会产生背压。&lt;/p>
&lt;p>多年来，我们一直使用基于 GPU 加速的 OpenCV 流水线运行在 x86 边缘节点上。方案确实能用——但仅限低负载场景。当检测分辨率提升到 8K 级别后，单帧处理延迟偶尔飙升至 180~220ms。这 180ms 的抖动意味着漏检、误放行和产线拥塞。根本原因在于架构：通用 GPU 流水线无论怎样优化，都无法消除调度延迟和 PCIe 传输开销的不确定性。&lt;/p>
&lt;h2 id="2-为什么选择-fpga硬件流水线的不可替代性">2. 为什么选择 FPGA：硬件流水线的不可替代性&lt;/h2>
&lt;p>我们决定将整条检测流水线下沉到 FPGA，让芯片直接对接图像传感器的 MIPI 接口。核心理由有三：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>流式架构&lt;/strong>：像素从 ADC 输出的那一刻就开始逐级流过流水线，不存在帧缓存、DMA 传输和内核上下文切换。&lt;/li>
&lt;li>&lt;strong>确定性延迟&lt;/strong>：精心设计的流水线具有精确的 &lt;code>N&lt;/code> 个时钟周期的像素延迟，&lt;code>N&lt;/code> 即流水线总深度，不存在方差。&lt;/li>
&lt;li>&lt;strong>能效优势&lt;/strong>：FPGA 方案功耗约 4W，而同等工作负载在 GPU 边缘节点上需要 45W。&lt;/li>
&lt;/ul>
&lt;h2 id="3-架构设计">3. 架构设计&lt;/h2>
&lt;p>我们在 Xilinx Kintex-7 上实现了五级流水线：&lt;/p>
&lt;pre tabindex="0">&lt;code>Sensor → Bayer2RGB → 色彩校正 → 二值化/阈值 → 连通域检测 → 判定锁存
 ↓ ↓ ↓ ↓ ↓ ↓
 MIPI 第1级 第2级 第3级 第4级 第5级
 (原始) (3周期) (2周期) (1周期) (8周期) (1周期)
&lt;/code>&lt;/pre>&lt;p>总流水线深度：&lt;strong>15 个时钟周期&lt;/strong>。在 200MHz 时钟下，单个像素从进入到缺陷标志输出的延迟为 &lt;strong>75 纳秒&lt;/strong>。整帧图像以行速率流转，8K × 8K 的帧在传感器输出完成的同时即完成检测——&lt;strong>不引入任何额外延迟&lt;/strong>。&lt;/p></description></item><item><title>裸机 Rust 实战：重写 Bootloader，零内存错误的背后</title><link>https://www.tainuohc.com/blog/2026-07-19-rust-embedded/</link><pubDate>Sun, 19 Jul 2026 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/blog/2026-07-19-rust-embedded/</guid><description>&lt;h2 id="1-bootloader-的困局">1. Bootloader 的困局&lt;/h2>
&lt;p>泰诺的工业控制板卡上搭载一颗 MCU，负责电源时序管理、看门狗和 OTA 固件升级。Bootloader 约 8000 行 C 代码，已&amp;quot;稳定&amp;quot;运行多年——但在嵌入式语境下，&amp;ldquo;稳定&amp;quot;往往意味着&amp;quot;已知的坑都补上了，未知的等待下一次现场翻车&amp;rdquo;。&lt;/p>
&lt;p>每隔 6 到 9 个月，新的 OTA 边界场景就会浮现：固件镜像校验时的缓冲区溢出、Flash 擦除例程的 off-by-one、CAN 总线命令分发器中的 use-after-free。每一次修复都需要全量 OTA 推送，而针对已部署的工控设备群，一次全量 OTA 是以周为单位的痛苦过程。&lt;/p>
&lt;h2 id="2-为什么要在裸机上用-rust">2. 为什么要在裸机上用 Rust&lt;/h2>
&lt;p>2026 年初，团队决定用 Rust 重写 Bootloader，目标平台为 &lt;code>thumbv7em-none-eabihf&lt;/code>（ARM Cortex-M7）。这个决定不是出于技术信仰，而是工程经济学的考量：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>编译期内存安全&lt;/strong>：借用检查器在编译阶段就消灭了 use-after-free、double-free 和缓冲区溢出——这几类问题占据了我们 Bootloader CVE 的约 70%。&lt;/li>
&lt;li>&lt;strong>零成本抽象&lt;/strong>：Rust 的 &lt;code>no_std&lt;/code> 生态提供了 RAII、类型化状态机和穷尽模式匹配，却不引入任何运行时和分配器。&lt;/li>
&lt;li>&lt;strong>生态成熟度&lt;/strong>：&lt;code>embedded-hal&lt;/code>、&lt;code>cortex-m&lt;/code>、&lt;code>cortex-m-rt&lt;/code>、&lt;code>probe-rs&lt;/code> 已达到生产级水准，有活跃维护和商业支持。&lt;/li>
&lt;/ul>
&lt;h2 id="3-架构设计">3. 架构设计&lt;/h2>
&lt;p>我们的 Bootloader 被建模为一个类型级强制的有限状态机：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-rust" data-lang="rust">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">enum&lt;/span> &lt;span style="color:#a6e22e">BootState&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PowerOn(Peripherals),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> VerifyFirmware(Peripherals, FlashHandle),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ReadyToBoot(VerifiedImage),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Fallback(Error),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">impl&lt;/span> BootState {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fn&lt;/span> &lt;span style="color:#a6e22e">transition&lt;/span>(self) -&amp;gt; &lt;span style="color:#a6e22e">Self&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">match&lt;/span> self {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Self::PowerOn(p) &lt;span style="color:#f92672">=&amp;gt;&lt;/span> self.init_peripherals(p),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Self::VerifyFirmware(p, f) &lt;span style="color:#f92672">=&amp;gt;&lt;/span> self.verify_image(p, f),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 编译器保证每个状态都被处理
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>核心设计原则：&lt;strong>非法状态不可表达&lt;/strong>。你不可能在镜像校验尚未完成时意外调用 &lt;code>jump_to_firmware()&lt;/code>，因为该函数只接受 &lt;code>VerifiedImage&lt;/code> 类型——而构造该类型的唯一路径就是通过校验流程。&lt;/p></description></item><item><title>从自建协议到 gRPC：泰诺异构工控系统通信架构的重大跨越</title><link>https://www.tainuohc.com/blog/2026-07-18-grpc-migration/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/blog/2026-07-18-grpc-migration/</guid><description>&lt;h2 id="1-为什么我们必须废除原有的自建私有协议">1. 为什么我们必须废除原有的自建私有协议？&lt;/h2>
&lt;p>在过去很长一段时间里，泰诺的工业控制板卡与上位机、打印控制端之间采用的是自建的私有协议。这类协议在特定单一品类开发中确实具备轻量、灵活、随改随用的特点。然而，随着泰诺逐步迈向工业 5.0 标准，我们的系统架构正在向&amp;quot;通用工业级异构计算与智能控制平台&amp;quot;产生蜕变。&lt;/p>
&lt;p>在这种背景下，原有私有协议暴露出三大难以调和的痛点：多语言 SDK 维护成本高昂（C#、C++/Rust 异构端需手动重写解析流）、缺乏强大的类型安全边界、异构系统难以灵活扩展 RPC 服务。&lt;/p>
&lt;h2 id="2-为什么选择-grpc--protobuf">2. 为什么选择 gRPC + Protobuf？&lt;/h2>
&lt;p>截至 2026 年 7 月，团队迎来重大里程碑：我们彻底抛弃了过去的自建协议与 JSON，全线切换为 gRPC 通信框架。并在所有联调测试和极限压力测试中取得了全部通过（Pass）的成绩。&lt;/p>
&lt;p>gRPC 给泰诺带来了质的飞跃：契约先行（Schema-First）、二进制序列化带来的极致高性能与低延迟。同时无缝对接了董佳琦主导的 tincore 核心&amp;quot;氧化计划&amp;quot;，通过 Rust 生态的 tonic 库完美适配，兼顾了极致性能与绝对内存安全。&lt;/p></description></item><item><title>参数读写</title><link>https://www.tainuohc.com/zh/docs/sdk/c/params/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/zh/docs/sdk/c/params/</guid><description>&lt;h1 id="参数读写">参数读写&lt;/h1>
&lt;p>读写服务端配置参数，通过 Key 命名约定区分全局参数与驱动板参数。&lt;/p>
&lt;h2 id="tijet_client_set_parameter">&lt;code>tijet_client_set_parameter&lt;/code>&lt;/h2>
&lt;p>设置参数。&lt;/p>
&lt;p>通过 Key 命名约定区分全局参数与驱动板参数：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;code>&amp;quot;key&amp;quot;&lt;/code> → 全局参数&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>&amp;quot;board.N.key&amp;quot;&lt;/code> → 驱动板编号 N 的参数（N 为板号）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>client&lt;/strong>: 客户端句柄。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>key&lt;/strong>: 参数名（区分大小写，UTF-8 编码），支持层级命名。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>value&lt;/strong>: 参数值（JSON 字符串格式，参见 param_type_convention 节）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_OK&lt;/code> — 设置成功&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_ERR_NOT_CONNECTED&lt;/code> — 未连接&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_ERR_INVALID_ARG&lt;/code> — client / key / value 为 NULL&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_ERR_SERVER&lt;/code> — 服务端拒绝该参数&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>使用示例：&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 全局参数
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">tijet_client_set_parameter&lt;/span>(cli, &lt;span style="color:#e6db74">&amp;#34;print_speed&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;100&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 驱动板 #0 的参数
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#a6e22e">tijet_client_set_parameter&lt;/span>(cli, &lt;span style="color:#e6db74">&amp;#34;board.0.print_speed&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;80&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>参见&lt;/strong>: &lt;code>tijet_client_get_parameter()&lt;/code>&lt;/p>
&lt;h2 id="tijet_client_get_parameter">&lt;code>tijet_client_get_parameter&lt;/code>&lt;/h2>
&lt;p>获取参数（单次调用）。&lt;/p>
&lt;p>不再需要&amp;quot;两次调用&amp;quot;模式——调用方预分配至少 &lt;a href="https://www.tainuohc.com/zh/docs/sdk/c/params/#TIJET_MAX_PARAM_VALUE">&lt;code>TIJET_MAX_PARAM_VALUE&lt;/code>&lt;/a> 字节的缓冲区，
传入 &lt;code>buf_size&lt;/code> 告知容量，一次调用即可获取参数值。&lt;/p>
&lt;p>通过 Key 命名约定区分全局参数与驱动板参数（同 set）。&lt;/p></description></item><item><title>错误码</title><link>https://www.tainuohc.com/zh/docs/sdk/c/error_codes/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/zh/docs/sdk/c/error_codes/</guid><description>&lt;h1 id="错误码">错误码&lt;/h1>
&lt;p>所有接口函数返回的状态码。&lt;/p>
&lt;h2 id="tijet_ok">&lt;code>TIJET_OK&lt;/code>&lt;/h2>
&lt;p>操作成功。&lt;/p>
&lt;h2 id="tijet_err_invalid_arg">&lt;code>TIJET_ERR_INVALID_ARG&lt;/code>&lt;/h2>
&lt;p>参数无效（NULL 指针、越界等）。&lt;/p>
&lt;h2 id="tijet_err_not_connected">&lt;code>TIJET_ERR_NOT_CONNECTED&lt;/code>&lt;/h2>
&lt;p>尚未连接到服务器。&lt;/p>
&lt;h2 id="tijet_err_timeout">&lt;code>TIJET_ERR_TIMEOUT&lt;/code>&lt;/h2>
&lt;p>操作超时。&lt;/p>
&lt;h2 id="tijet_err_buffer_too_small">&lt;code>TIJET_ERR_BUFFER_TOO_SMALL&lt;/code>&lt;/h2>
&lt;p>用户提供的缓冲区太小。调用 tijet_client_last_error() 获取所需大小。&lt;/p>
&lt;h2 id="tijet_err_server">&lt;code>TIJET_ERR_SERVER&lt;/code>&lt;/h2>
&lt;p>服务器返回错误。&lt;/p>
&lt;h2 id="tijet_err_internal">&lt;code>TIJET_ERR_INTERNAL&lt;/code>&lt;/h2>
&lt;p>内部错误（内存不足、线程错误等）。&lt;/p>
&lt;h2 id="tijet_client_t">&lt;code>tijet_client_t&lt;/code>&lt;/h2>
&lt;p>客户端句柄（opaque pointer）。&lt;/p>
&lt;p>通过 tijet_client_create() 创建，用毕后必须调用 tijet_client_destroy() 释放。
内部结构不对外暴露，所有操作均通过本头文件中声明的函数完成。&lt;/p></description></item><item><title>打印数据</title><link>https://www.tainuohc.com/zh/docs/sdk/c/print_data/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/zh/docs/sdk/c/print_data/</guid><description>&lt;h1 id="打印数据">打印数据&lt;/h1>
&lt;p>向服务端发送打印数据。&lt;/p>
&lt;h2 id="tijet_client_set_print_data">&lt;code>tijet_client_set_print_data&lt;/code>&lt;/h2>
&lt;p>发送打印数据到服务端。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>client&lt;/strong>: 客户端句柄。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>data&lt;/strong>: 二进制打印数据缓冲区。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>data_len&lt;/strong>: 数据长度（字节）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>index&lt;/strong>: 打印数据序号（由业务层传入，用于去重 / 顺序控制）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>is_static&lt;/strong>: 0 = 普通数据（一次性打印），非 0 = 静态数据（重复打印同一份）。
静态数据会被服务端缓存以优化重复传输。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>checksum_buf&lt;/strong> &lt;em>(输出)&lt;/em>: 校验和输出缓冲区。传 NULL 表示不关心校验和。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>checksum_buf_size&lt;/strong> &lt;em>(输入/输出)&lt;/em>:&lt;/p>
&lt;/li>
&lt;li>
&lt;p>[in] checksum_buf 的容量（字节）。checksum_buf 为 NULL 时传 0。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>[out] 校验和字符串所需字节数（含结尾 \0）。
当前实现输出 64 字符的 SHA-256 十六进制字符串，需要 65 字节。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_OK&lt;/code> — 发送成功&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_ERR_NOT_CONNECTED&lt;/code> — 未连接&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_ERR_INVALID_ARG&lt;/code> — client / data 为 NULL 或 data_len 为 0&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_ERR_BUFFER_TOO_SMALL&lt;/code> — checksum_buf 不足&lt;/p></description></item><item><title>连接管理</title><link>https://www.tainuohc.com/zh/docs/sdk/c/connection/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/zh/docs/sdk/c/connection/</guid><description>&lt;h1 id="连接管理">连接管理&lt;/h1>
&lt;p>建立与断开打印服务的连接。&lt;/p>
&lt;h2 id="tijet_client_connect">&lt;code>tijet_client_connect&lt;/code>&lt;/h2>
&lt;p>连接到打印服务。&lt;/p>
&lt;p>自动识别地址类型：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>包含 &lt;code>:&lt;/code> 后跟数字端口（如 &lt;code>127.0.0.1:50051&lt;/code>）→ TCP 直连&lt;/p>
&lt;/li>
&lt;li>
&lt;p>其他非空字符串 → Unix Domain Socket（自动添加 &lt;code>unix:&lt;/code> 前缀）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>NULL 或空字符串 → 使用 &lt;a href="https://www.tainuohc.com/zh/docs/sdk/c/connection/#TIJET_DEFAULT_UDS_PATH">&lt;code>TIJET_DEFAULT_UDS_PATH&lt;/code>&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>client&lt;/strong>: 客户端句柄。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>address&lt;/strong>: 服务地址（UTF-8 字符串）。传 NULL 或空字符串使用默认 UDS 路径。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_OK&lt;/code> — 连接成功&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_ERR_INVALID_ARG&lt;/code> — client 为 NULL&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_ERR_TIMEOUT&lt;/code> — 连接超时&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_ERR_SERVER&lt;/code> — 服务端拒绝连接&lt;/p>
&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>注意&lt;/strong>: 重复调用会先断开当前连接再重新连接。
&lt;strong>参见&lt;/strong>: &lt;code>tijet_client_disconnect()&lt;/code>
&lt;strong>参见&lt;/strong>: &lt;code>TIJET_DEFAULT_UDS_PATH&lt;/code>&lt;/p>&lt;/blockquote>
&lt;h2 id="tijet_client_disconnect">&lt;code>tijet_client_disconnect&lt;/code>&lt;/h2>
&lt;p>断开与服务端的连接。&lt;/p>
&lt;p>自动停止事件订阅和日志订阅（若已启动），安全关闭 gRPC channel。
未连接时调用是安全的（空操作）。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>client&lt;/strong>: 客户端句柄。&lt;/li>
&lt;li>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_OK&lt;/code> — 断开成功&lt;/li>
&lt;li>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_ERR_INVALID_ARG&lt;/code> — client 为 NULL&lt;/li>
&lt;/ul>
&lt;h2 id="tijet_client_is_connected">&lt;code>tijet_client_is_connected&lt;/code>&lt;/h2>
&lt;p>查询是否已连接。&lt;/p></description></item><item><title>日志订阅</title><link>https://www.tainuohc.com/zh/docs/sdk/c/logs/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/zh/docs/sdk/c/logs/</guid><description>&lt;h1 id="日志订阅">日志订阅&lt;/h1>
&lt;p>订阅服务端的远程日志推送（WatchLogs）。&lt;/p>
&lt;h2 id="tijet_client_set_log_callback">&lt;code>tijet_client_set_log_callback&lt;/code>&lt;/h2>
&lt;p>注册服务端日志回调。&lt;/p>
&lt;p>必须在 tijet_client_start_log_subscription() 之前调用。
如未注册，日志默认写入客户端 spdlog（需链接 spdlog 库）。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>client&lt;/strong>: 客户端句柄。&lt;/li>
&lt;li>&lt;strong>callback&lt;/strong>: 日志回调函数。传 NULL 恢复默认（spdlog 输出）。&lt;/li>
&lt;li>&lt;strong>user_data&lt;/strong>: 用户自定义数据，回调时原样传入。可以为 NULL。&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>注意&lt;/strong>: 回调在内部日志线程中执行，务必保证线程安全。
&lt;strong>参见&lt;/strong>: &lt;code>tijet_log_callback_t&lt;/code>
&lt;strong>参见&lt;/strong>: &lt;code>tijet_client_start_log_subscription()&lt;/code>&lt;/p>&lt;/blockquote>
&lt;h2 id="tijet_client_stop_log_subscription">&lt;code>tijet_client_stop_log_subscription&lt;/code>&lt;/h2>
&lt;p>停止日志订阅。&lt;/p>
&lt;p>安全关闭日志流并等待内部线程退出，阻塞直到线程完全退出。
未启动订阅时调用是安全的（空操作）。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>client&lt;/strong>: 客户端句柄。&lt;/li>
&lt;li>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_OK&lt;/code> — 停止成功&lt;/li>
&lt;li>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_ERR_INVALID_ARG&lt;/code> — client 为 NULL&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>参见&lt;/strong>: &lt;code>tijet_client_start_log_subscription()&lt;/code>&lt;/p></description></item><item><title>生命周期</title><link>https://www.tainuohc.com/zh/docs/sdk/c/lifecycle/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/zh/docs/sdk/c/lifecycle/</guid><description>&lt;h1 id="生命周期">生命周期&lt;/h1>
&lt;p>客户端句柄的创建与销毁。&lt;/p>
&lt;h2 id="tijet_client_create">&lt;code>tijet_client_create&lt;/code>&lt;/h2>
&lt;p>创建客户端实例。&lt;/p>
&lt;p>分配并初始化内部资源。返回的句柄在使用前需要调用 connectToServer()
建立与服务端的连接。&lt;/p>
&lt;p>&lt;strong>返回&lt;/strong>: 非 NULL 的客户端句柄。内部内存分配失败时返回 NULL。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>注意&lt;/strong>: 用毕后必须调用 tijet_client_destroy() 释放。&lt;/p>&lt;/blockquote>
&lt;p>&lt;strong>参见&lt;/strong>: &lt;code>tijet_client_destroy()&lt;/code>&lt;/p>
&lt;h2 id="tijet_client_destroy">&lt;code>tijet_client_destroy&lt;/code>&lt;/h2>
&lt;p>销毁客户端实例。&lt;/p>
&lt;p>自动断开连接（如有）、释放所有内部资源。
传入 NULL 是安全的（空操作）。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>client&lt;/strong>: 客户端句柄。可以为 NULL。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>参见&lt;/strong>: &lt;code>tijet_client_create()&lt;/code>&lt;/p></description></item><item><title>事件订阅</title><link>https://www.tainuohc.com/zh/docs/sdk/c/events/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/zh/docs/sdk/c/events/</guid><description>&lt;h1 id="事件订阅">事件订阅&lt;/h1>
&lt;p>订阅服务端的异步事件推送。&lt;/p>
&lt;h2 id="tijet_client_set_event_callback">&lt;code>tijet_client_set_event_callback&lt;/code>&lt;/h2>
&lt;p>注册服务端事件回调。&lt;/p>
&lt;p>必须在 tijet_client_start_event_subscription() 之前调用。
未注册回调时启动事件订阅不会产生效果。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>client&lt;/strong>: 客户端句柄。&lt;/li>
&lt;li>&lt;strong>callback&lt;/strong>: 事件回调函数。传 NULL 取消注册。&lt;/li>
&lt;li>&lt;strong>user_data&lt;/strong>: 用户自定义数据，回调时原样传入。可以为 NULL。&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>注意&lt;/strong>: 回调在内部事件线程中执行，务必保证线程安全。
&lt;strong>参见&lt;/strong>: &lt;code>tijet_event_callback_t&lt;/code>
&lt;strong>参见&lt;/strong>: &lt;code>tijet_client_start_event_subscription()&lt;/code>&lt;/p>&lt;/blockquote>
&lt;h2 id="tijet_client_stop_event_subscription">&lt;code>tijet_client_stop_event_subscription&lt;/code>&lt;/h2>
&lt;p>停止事件订阅。&lt;/p>
&lt;p>安全关闭事件流并等待内部线程退出，阻塞直到线程完全退出。
未启动订阅时调用是安全的（空操作）。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>client&lt;/strong>: 客户端句柄。&lt;/li>
&lt;li>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_OK&lt;/code> — 停止成功&lt;/li>
&lt;li>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_ERR_INVALID_ARG&lt;/code> — client 为 NULL&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>参见&lt;/strong>: &lt;code>tijet_client_start_event_subscription()&lt;/code>&lt;/p></description></item><item><title>网络配置</title><link>https://www.tainuohc.com/zh/docs/sdk/c/network/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/zh/docs/sdk/c/network/</guid><description>&lt;h1 id="网络配置">网络配置&lt;/h1>
&lt;p>配置设备网络（IP / 掩码 / 网关）。&lt;/p>
&lt;h2 id="tijet_client_network_config">&lt;code>tijet_client_network_config&lt;/code>&lt;/h2>
&lt;p>配置设备网络参数。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>client&lt;/strong>: 客户端句柄。&lt;/li>
&lt;li>&lt;strong>ip&lt;/strong>: IPv4 地址（如 &lt;code>&amp;quot;192.168.1.100&amp;quot;&lt;/code>）。传 NULL 或空字符串跳过。&lt;/li>
&lt;li>&lt;strong>ip_mask&lt;/strong>: 子网掩码（如 &lt;code>&amp;quot;255.255.255.0&amp;quot;&lt;/code>）。传 NULL 或空字符串跳过。&lt;/li>
&lt;li>&lt;strong>gateway&lt;/strong>: 默认网关（如 &lt;code>&amp;quot;192.168.1.1&amp;quot;&lt;/code>）。传 NULL 或空字符串跳过。&lt;/li>
&lt;li>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_OK&lt;/code> — 配置成功&lt;/li>
&lt;li>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_ERR_NOT_CONNECTED&lt;/code> — 未连接&lt;/li>
&lt;li>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_ERR_SERVER&lt;/code> — 服务端返回配置错误&lt;/li>
&lt;li>&lt;strong>返回&lt;/strong>: &lt;code>TIJET_ERR_INVALID_ARG&lt;/code> — client 为 NULL&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>注意&lt;/strong>: 三个参数中至少应提供一个非空值。全为空将返回 TIJET_ERR_INVALID_ARG。&lt;/p>&lt;/blockquote></description></item><item><title>元信息</title><link>https://www.tainuohc.com/zh/docs/sdk/c/meta/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/zh/docs/sdk/c/meta/</guid><description>&lt;h1 id="元信息">元信息&lt;/h1>
&lt;p>错误信息查询与版本信息。&lt;/p>
&lt;h2 id="tijet_client_last_error">&lt;code>tijet_client_last_error&lt;/code>&lt;/h2>
&lt;p>获取最近一次错误的可读描述。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>client&lt;/strong>: 客户端句柄。
&lt;strong>返回&lt;/strong>: 指向静态错误字符串的指针。
无错误时返回 &lt;code>&amp;quot;No error&amp;quot;&lt;/code>。
该指针在下次对同一句柄的操作前一直有效。&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>注意&lt;/strong>: 返回的字符串不需要（也不能）由调用方释放。&lt;/p>&lt;/blockquote>
&lt;h2 id="tijet_client_version">&lt;code>tijet_client_version&lt;/code>&lt;/h2>
&lt;p>获取 SDK 版本号。&lt;/p>
&lt;p>&lt;strong>返回&lt;/strong>: 版本字符串，格式 &lt;code>&amp;quot;major.minor.patch&amp;quot;&lt;/code>，如 &lt;code>&amp;quot;1.0.0&amp;quot;&lt;/code>。
返回的字符串为静态常量，不可释放。&lt;/p>
&lt;h2 id="tijet_version_major">&lt;code>TIJET_VERSION_MAJOR&lt;/code>&lt;/h2>
&lt;p>SDK 主版本号。&lt;/p>
&lt;h2 id="tijet_version_minor">&lt;code>TIJET_VERSION_MINOR&lt;/code>&lt;/h2>
&lt;p>SDK 次版本号。&lt;/p>
&lt;h2 id="tijet_version_patch">&lt;code>TIJET_VERSION_PATCH&lt;/code>&lt;/h2>
&lt;p>SDK 修订版本号。&lt;/p>
&lt;h2 id="tijet_sha256_hex_len">&lt;code>TIJET_SHA256_HEX_LEN&lt;/code>&lt;/h2>
&lt;p>SHA-256 十六进制字符串长度（不含结尾 \0）。&lt;/p></description></item></channel></rss>