<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>不吐不快</title>
  
  <subtitle>我心中的一团火是不会熄地</subtitle>
  <link href="https://mianao.info/atom.xml" rel="self"/>
  
  <link href="https://mianao.info/"/>
  <updated>2026-04-07T10:00:00.000Z</updated>
  <id>https://mianao.info/</id>
  
  <author>
    <name>Harry</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>岗上有一棵树</title>
    <link href="https://mianao.info/history-tree/"/>
    <id>https://mianao.info/history-tree/</id>
    <published>2026-04-07T10:00:00.000Z</published>
    <updated>2026-04-07T10:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>岗上有一棵树，长在路边，我一直觉得它很特别，细小但结实，我们方言叫榨树。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/2025summer/IMG_20250708_181649.webp" alt="IMG_20250708_181649"><br>它旁边的这户人家姓林，再往南就是我三伯家，挨着就是我家了，从我家过去也是两兄弟的房子，是我妈的表弟。路靠北边就是我舅舅家，顺着路向西，只有两家，一家是我同学，也姓林，另一家是小卖部。</p><p>岗上从我记事起就住着这几家，我三伯搬走的最早，我上学前班前他们一家就去镇上住了，房子就一直是我爷爷奶奶在住。</p><p>一道东西向的土墙隔着邻居的排水沟，后面的园子也是长长的荆条篱笆，围着各自的领地。旁边这家男主人叫三福，在我有限的记忆力他似乎没有跟人聊过天，说过话，除了田里干活，捞鱼摸虾，砍树捆柴，就没见他干过别的。<br>女主人个子高高的，身量苗条，圆圆的脸蛋，嘴巴很厉害。他们家有两个女儿，一个应该比我小两三岁吧，老二更小，我基本没印象。</p><p>一般来说，在农村这种熟人社会，谁家来自哪里，亲戚关系等等都是清清楚楚的，但是他们家完全不知道，三福没有兄弟姐妹，他爸妈大家都叫林家老头，林家婆婆。再往上数，好像没人知道了，女主人是我们镇附近的村嫁过来的，比较远。</p><p>三福虽然在我看来沉默寡言，也不在公众场合出现，但他坐过牢，为什么呢，据说是在八十年代初严打的时候，摸了村里一个女奶子。是不是真的摸了，摸了谁，我没去考证过，反正我记事的时候他已经在家了。</p><p>不知道为什么我会很讨厌他们家，从来不和他家的小孩玩。有可能是从他家门前过时，狗总是追着咬我吧。记得有一次我从堰塘边回来，路过他家的地，看到长了几个南瓜，就用镰刀去割了几刀，只划破不割断。结果被邻居看到了，跟我奶奶说了，我奶奶把我叫去说了一顿。</p><p>有一天下着大雨，到处涨水，田里都是鱼。村里好多人都去抓鱼，我也跟着我爸去了，在路边转来转去的看，也不能下水。刚好我同学也在，他说他哥在那边叉鱼，我们去看看。我跟着他沿着堰塘边走过去，堰堤上都是被人一锹一锹翻的黄泥，滑得很。我一不留神，直接摔进了堰塘里，平时那地方水还不深，但下雨涨水，堰里是满满当当。我没有哭也没叫，睁眼看到的都是浑黄的水，一口气憋着，往下沉了好久好久，感觉自己快要死了，突然一只手抓住了我的领口，把我拎了上来。湿淋淋地站在路上，才知道是三福救了我。后来我爸妈是怎么找来的，我又是怎么回家的，都不记得了。但我可以肯定，我爸妈应该没有拿东西去专程感谢三福。</p><p>后来的记忆是三福被打。起因是为啥我已经不太记得了，好像是因为宅基地的事。他们家和我舅舅家隔了一条马路，正常来说不会有宅基地的矛盾，应该是他们家要在我舅舅家正门口盖一排猪圈。那个女的吵架很厉害，和我舅妈吵了吧，打没打我就忘记了。我舅舅只有一只胳膊，肯定是打不过的，于是就叫了一帮人过来。那天晚上一堆人把三福两口子拖到稻场上打，我去看的时候，三福满脸是血，跪在地上求饶：爹爹们啊，别打了，我要死了。<br>三福被打的有多严重我也不记得了，事后我舅舅被撤掉了小学校长的职务，留党察看，三福的一排猪圈还是建了起来。这件事似乎也没有赢家。</p><p>过了没几年吧，三福一家就说要搬走了，那时云贵川很多移民到我们村，他们把房子卖了，说是搬去了女的娘家那边了。从此以后，我就再没见过他们了。</p><p>我上初中的时候，我们家也把房子卖给了一家云南的兄弟，搬到了岗对面的小学旁边了。</p><p>我家南边隔壁的两兄弟，我应该叫表舅的，但关系一般，因为他们自己兄弟四五个，关系也不好。记得一次我睡午觉被吵醒了，起来一看隔壁稻场全是人，他们兄弟，媳妇，爹妈，都在那边吵。手里有的拿着菜刀有的拿着叉，不过好像没见血。后来才知道他们是分家分出的矛盾。先搬走的是老二，他的手有残疾，老二媳妇的腿脚也有残疾，他们也搬到了媳妇娘家住了。没过两年，老大也搬走了，也是去了媳妇娘家那边，虽然老大的脑子有点不好，但他儿子还可以，后来考上了大学，他媳妇跟着去带孙子去了。至于老大，他开个三轮车到处收废品，前两年还听说偷拿别人的东西被打了。</p><p>到我上高中的时候，镇里已经开始合并小学了，我舅舅也被调到镇上去当老师了。后来他就把房子卖了，也是卖给了云贵川的移民，买了原来村小的宿舍住了。表哥毕业赚钱后就给他们在镇上小学前面买了房子，村小的宿舍前几年也卖了。</p><p>小时候最喜欢去的就是那家小卖部，那里有卖蛋心圆，汽水。老板是老两口，老头姓李，脾气有点拐，老太太很少说话。小卖部还是茶馆，年轻人打牌，老头喝茶，喝酒，弄点花生米就能喝半天。还有一个瞎子，会算命，有时候我就听他在那车轱辘话翻来覆去。他还会天气预报，有个小鼓，敲一敲，听一听，就说明天会不会下雨。</p><p>到我上初中的时候，小卖部就很冷清了，我也再没去过。老太太是先死的，十几年前老头也死了，死的时候没有人知道，不知道过了多久，他儿子去找他时才发现的。最后一锹一锹的铲出来的，基本上就剩骨头了。</p><p>小卖部旁边是我同学的房子，她已经移民澳洲了，她弟弟也定居外地了，爸妈就过去给他们带孩子，也不在这住了。前几年把她家和小卖部地合起来盖了一栋新房子，估计是她爸妈想老了回来自己住吧。</p><p>一晃四十年过去了，这棵树还是只有碗口粗，高也就四五米，你看它张扬的枝叶，挺拔的躯干，生命力喷薄而出，而我已经是头发渐白的中年人了。每次回家都会看到这棵树，我就在想，要是那次我掉堰里没人看见会怎样，三福没救我会怎样，我这四十年是否就是捡来的？三福不知道现在怎么样了，也快七十了吧，希望他一切都好。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;岗上有一棵树，长在路边，我一直觉得它很特别，细小但结实，我们方言叫榨树。&lt;br&gt;&lt;img src=&quot;data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7&quot; data-la</summary>
      
    
    
    
    <category term="Live" scheme="https://mianao.info/categories/Live/"/>
    
    
    <category term="生老病死" scheme="https://mianao.info/tags/%E7%94%9F%E8%80%81%E7%97%85%E6%AD%BB/"/>
    
  </entry>
  
  <entry>
    <title>OpenWrt 要你命三千版</title>
    <link href="https://mianao.info/openwrt-multi-proxy-plugin/"/>
    <id>https://mianao.info/openwrt-multi-proxy-plugin/</id>
    <published>2026-04-01T02:00:00.000Z</published>
    <updated>2026-04-01T02:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>自从使用软路由后就一直是自己编译的 OpenWrt 固件，因为常见的 X86 固件都不太合我胃口。顺便说下，不知道为啥到处都是 ImmortalWrt 的固件，我就没觉得好用呢。还有个 ImmortalWrt Image Builder 整的比编译固件还复杂，我是没搞明白，没有的 ipk 还要自己去找，真是脱裤子放屁。</p><p><a href="https://mianao.info/7bf3cb8/">轻松编译Openwrt固件支持V2ray和Trojan</a></p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/OpenWrt3/lede.webp" alt="lede"></p><p>分享一个 OpenWrt x86 版本，我称之为要你命三千，什么 passw*，passw* 2，nekob*，homepro*，ssrplu*，opencla*，momo 能整的都给整上，就问你怕不怕。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/OpenWrt3/services.webp" alt="services"></p><p>当然会有 istore 和 Docker。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/OpenWrt3/istore.webp" alt="istore"></p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/OpenWrt3/docker.webp" alt="docker"></p><p>还有网络控制，应用过滤等。<br>我一直觉得不要把 NAS，HomeAssistant，软路由搞一起，一个出问题可能就全部搞挂掉，排除故障也麻烦，所以我编译的 OpenWrt 就是上网。<br>百度网盘分享：<a href="https://pan.baidu.com/s/1BTbtZJQvvUXtfwCAknNJdQ?pwd=xg1r">https://pan.baidu.com/s/1BTbtZJQvvUXtfwCAknNJdQ?pwd=xg1r</a> 提取码: xg1r</p><p>openwrt-x86-64-generic-ext4-combined-efi-20260305.img.gz 夸克网盘链接：<a href="https://pan.quark.cn/s/6d6f25bd5a1e">https://pan.quark.cn/s/6d6f25bd5a1e</a> 提取码：XxaF</p><p>openwrt-x86-64-generic-squashfs-combined-efi-20260305.img.gz 夸克网盘链接：<a href="https://pan.quark.cn/s/7bff1dec76ce">https://pan.quark.cn/s/7bff1dec76ce</a> 提取码：8u8F</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;自从使用软路由后就一直是自己编译的 OpenWrt 固件，因为常见的 X86 固件都不太合我胃口。顺便说下，不知道为啥到处都是 ImmortalWrt 的固件，我就没觉得好用呢。还有个 ImmortalWrt Image Builder 整的比编译固件还复杂，我是没搞明白，</summary>
      
    
    
    
    <category term="DIY" scheme="https://mianao.info/categories/DIY/"/>
    
    
    <category term="OpenWrt" scheme="https://mianao.info/tags/OpenWrt/"/>
    
  </entry>
  
  <entry>
    <title>KiCad-立创插件国内优化版</title>
    <link href="https://mianao.info/kicad-tool-plugin-lcsc-cn/"/>
    <id>https://mianao.info/kicad-tool-plugin-lcsc-cn/</id>
    <published>2026-03-27T16:00:00.000Z</published>
    <updated>2026-03-27T16:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>前面我写了 <a href="https://mianao.info/kicad-tool-recommend/">KiCad常用插件推荐</a>，推荐了 KiCad 常用的一些插件。<br>我觉得最好用的就是立创相关的一些，比如立创商城封装库转 KiCad 库的，嘉立创生产加工的一键插件等。<br>这些插件有个共同的特点就是针对嘉立创海外网站的，lcsc.com，jlcpcb.com，easyeda.com 等网站，速度慢不说，库存价格这些都是海外的。<br>于是，我用 AI 改了两个我常用的插件，一个是专门导入立创封装的，一个是针对嘉立创 PCB 生产加工的，都支持最新的 KiCAD 10。</p><p>插件既可以使用压缩包安装，也可以使用添加库链接的方式安装（需要正常访问 GitHub）。</p><p><strong>我修改的插件库安装链接：</strong><br><a href="https://github.com/harry10086/kicad-repository">https://github.com/harry10086/kicad-repository</a></p><p>目前有四个插件：<br><strong>ViaStitching:</strong> Automatic via stitching generation for PCB zones (自动添加GND缝合孔 中文版)<br><strong>Interactive HTML BOM:</strong> Interactive BOM generator with multi-CAD support (HTML BOM 生成工具中文版 适合手焊)<br><strong>KiCAD JLCPCB Tools:</strong> 嘉立创 PCB SMT 工具<br><strong>JLCImport:</strong> JLCPCB&#x2F;立创商城库导入工具</p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/plugins.png" alt="plugins"></p><p>在扩展管理器里面添加库链接即可。<br>repository URL: <code>https://raw.githubusercontent.com/harry10086/kicad-repository/main/repository.json</code></p><h3 id="KiCAD-JLCPCB-tools-国内版"><a href="#KiCAD-JLCPCB-tools-国内版" class="headerlink" title="KiCAD JLCPCB tools 国内版"></a>KiCAD JLCPCB tools 国内版</h3><p>这是一个嘉立创打板和贴片的一键工具，生成嘉立创可用的 gerber 文件和 BOM，坐标文件。<br>原地址：<a href="https://github.com/Bouni/kicad-jlcpcb-tools">https://github.com/Bouni/kicad-jlcpcb-tools</a></p><p>原插件运行即下载国外的 lcsc.com 数据库文件，压缩包大小近 2G，需要可靠的网络，耗时比较久。<br>好处就是搜索特别快，因为数据库在本地，坏处是价格，库存都是国外的，而且实时性不好，数据打包依赖国外开发者。<br>我 fork 后改版的地址：<a href="https://github.com/harry10086/kicad-jlcpcb-tools">https://github.com/harry10086/kicad-jlcpcb-tools</a></p><ul><li>汉化了大多数按钮；</li><li>从立创商城实时搜索元器件，会有一点延时，而且一次加载数量不能太多，国内网站反爬虫很厉害；</li><li>减少了元件过滤规则；</li><li>元件详情页可以复制；</li></ul><p><strong>KiCAD-JLCPCB-tools 国内版.zip 百度云链接:</strong> <a href="https://pan.baidu.com/s/1uk-77LpDFXMov0UyXq4dag?pwd=9sfg">https://pan.baidu.com/s/1uk-77LpDFXMov0UyXq4dag?pwd=9sfg</a><br>提取码: 9sfg</p><p><strong>KiCAD-JLCPCB-tools 国内版 夸克网盘链接：</strong><a href="https://pan.quark.cn/s/49af8e59def2">https://pan.quark.cn/s/49af8e59def2</a></p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/jlcpcb-tools-zh.png" alt="jlcpcb-tools-zh"></p><h3 id="JLCImport-国内版"><a href="#JLCImport-国内版" class="headerlink" title="JLCImport 国内版"></a>JLCImport 国内版</h3><p>lcsc.com 元件搜索，导入 easyeda.com 封装到 KiCAD 库，可选择导入项目库或自定义库。<br>有 plugin, CLI, GUI, TUI 四种工作形式。<br>原地址：<a href="https://github.com/jvanderberg/kicad_jlcimport">https://github.com/jvanderberg/kicad_jlcimport</a></p><p>我 fork 后改版的地址：<a href="https://github.com/harry10086/kicad_jlcimport">https://github.com/harry10086/kicad_jlcimport</a></p><ul><li>搜索源换成 szlcsc.com；</li><li>增加了符号和封装预览；</li></ul><p><strong>JLCImport.zip 百度云链接:</strong> <a href="https://pan.baidu.com/s/1uk-77LpDFXMov0UyXq4dag?pwd=9sfg">https://pan.baidu.com/s/1uk-77LpDFXMov0UyXq4dag?pwd=9sfg</a><br>提取码: 9sfg</p><p><strong>JLCImport.zip 夸克网盘链接：</strong><a href="https://pan.quark.cn/s/3d432d330663">https://pan.quark.cn/s/3d432d330663</a></p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://github.com/harry10086/kicad_jlcimport/raw/main/images/search.png" alt="alt text"></p><h3 id="EasyKiConverter"><a href="#EasyKiConverter" class="headerlink" title="EasyKiConverter"></a>EasyKiConverter</h3><p>立创商城封装库转 KiCad 库：EasyKiConverter，这个不是插件，但也很好用，这个开发者是国内的。<br>开源地址：<a href="https://github.com/tangsangsimida/EasyKiConverter_QT">https://github.com/tangsangsimida/EasyKiConverter_QT</a><br>一个强大的 Python 工具，用于将嘉立创（LCSC）和 EasyEDA 元件转换为 KiCad 格式，支持符号、封装和 3D 模型的完整转换。提供现代化的 PyQt6 桌面界面，让元件转换变得简单高效。</p><ul><li>符号转换：将 EasyEDA 符号转换为 KiCad 符号库（.kicad_sym）</li><li>封装生成：从 EasyEDA 封装创建 KiCad 封装（.kicad_mod）</li><li>3D模型支持：自动下载并转换 3D 模型（支持多种格式）</li><li>批量处理：支持多个元件同时转换</li><li>网络重试机制：网络请求失败时自动重试，提高转换成功率<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/EasyKiConverter.webp" alt="EasyKiConverter"></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h3&gt;&lt;p&gt;前面我写了 &lt;a href=&quot;https://mianao.info/kicad-tool-recommend/&quot;&gt;KiCad常用插件推荐&lt;</summary>
      
    
    
    
    <category term="Work" scheme="https://mianao.info/categories/Work/"/>
    
    
    <category term="EDA" scheme="https://mianao.info/tags/EDA/"/>
    
    <category term="KiCAD" scheme="https://mianao.info/tags/KiCAD/"/>
    
  </entry>
  
  <entry>
    <title>KiCad 10.0.0 发布了</title>
    <link href="https://mianao.info/kicad-version-10.0.0-released/"/>
    <id>https://mianao.info/kicad-version-10.0.0-released/</id>
    <published>2026-03-20T16:00:00.000Z</published>
    <updated>2026-03-20T16:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>在经过了 RC1,RC2 两个版本后，<a href="https://www.kicad.org/blog/2026/03/Version-10.0.0-Released/">KiCad 10.0.0 正式版终于发布了！</a></p><h3 id="主要改进"><a href="#主要改进" class="headerlink" title="主要改进"></a>主要改进</h3><p><strong>通用</strong></p><ul><li>Windows 深色模式</li><li>可自定义工具栏</li><li>对话框中的撤销&#x2F;重做支持</li><li>套索选择<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/KiCAD2.png" alt="KiCAD2"></li><li>新增导入功能：PADS，Cadence Allegro <code>.brd</code><br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/KiCAD1.png" alt="KiCAD1"></li></ul><p><strong>原理图设计</strong></p><ul><li>Variants:一种跟踪同一项目的不同版本的方法，这些版本共享一个原理图，但具有属性更改（例如，不同的物料清单）。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/KiCAD3.png" alt="KiCAD3"></li><li>跳线显示：原理图中交叉不连接的地方<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/hopovers.png" alt="hopovers"></li><li>跳线支持：PCB 中跳线连接</li><li>分组支持</li><li>引脚列表 CSV 导入导出</li></ul><p><strong>PCB 设计</strong></p><ul><li>时域调整 ：支持定义时域约束而不是长度约束，以及支持调整配置文件，使用户能够为信号定义每层的布线参数。</li><li>PCB 设计模块<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/KiCAD5.png" alt="KiCAD5"></li><li>引脚和门交换</li><li>图形化设计规则编辑器</li><li>多边形的精确点编辑</li><li>DRC 错误的建议修复操作<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/KiCAD4.png" alt="KiCAD4"></li><li>3D PDF 导出</li><li>原生圆角矩形<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/KiCAD6.png" alt="KiCAD6"></li></ul><h3 id="强烈推荐立创商城元件库相关的插件"><a href="#强烈推荐立创商城元件库相关的插件" class="headerlink" title="强烈推荐立创商城元件库相关的插件"></a>强烈推荐立创商城元件库相关的插件</h3><p><strong>我修改的插件库安装链接：</strong><br><a href="https://github.com/harry10086/kicad-repository">https://github.com/harry10086/kicad-repository</a></p><p>目前有四个插件：<br><strong>ViaStitching:</strong> Automatic via stitching generation for PCB zones (自动添加GND缝合孔 中文版)<br><strong>Interactive HTML BOM:</strong> Interactive BOM generator with multi-CAD support (HTML BOM 生成工具中文版 适合手焊)<br><strong>KiCAD JLCPCB Tools:</strong> 嘉立创 PCB SMT 工具<br><strong>JLCImport:</strong> JLCPCB&#x2F;立创商城库导入工具</p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/plugins.png" alt="plugins"></p><p>在扩展管理器里面添加库链接即可。<br>repository URL: <code>https://raw.githubusercontent.com/harry10086/kicad-repository/main/repository.json</code></p><p><strong>KiCAD JLCImport</strong><br>立创商城的元件库搜索，可直接导入项目库或自定义库。<br>原开源地址：<a href="https://github.com/jvanderberg/kicad_jlcimport">https://github.com/jvanderberg/kicad_jlcimport</a></p><p>我 fork 后改版的地址：<a href="https://github.com/harry10086/kicad_jlcimport">https://github.com/harry10086/kicad_jlcimport</a></p><ul><li>搜索源换成 szlcsc.com；</li><li>增加了符号和封装预览；</li></ul><p><strong>JLCImport.zip 百度云链接:</strong> <a href="https://pan.baidu.com/s/1uk-77LpDFXMov0UyXq4dag?pwd=9sfg">https://pan.baidu.com/s/1uk-77LpDFXMov0UyXq4dag?pwd=9sfg</a><br>提取码: 9sfg</p><p><strong>JLCImport.zip 夸克网盘链接：</strong><a href="https://pan.quark.cn/s/3d432d330663">https://pan.quark.cn/s/3d432d330663</a></p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://github.com/harry10086/kicad_jlcimport/raw/main/images/search.png" alt="alt text"></p><p><strong>KiCAD JLCPCB tools</strong><br>这是一个嘉立创打板和贴片的一键工具，很好用，生成嘉立创可用的 gerber 文件和 BOM。唯一不好的地方，数据库是国外的立创商城，价格和库存都和国内不一样。<br>开源地址：<br><a href="https://github.com/Bouni/kicad-jlcpcb-tools">https://github.com/Bouni/kicad-jlcpcb-tools</a></p><p>我 fork 后改版的地址：<a href="https://github.com/harry10086/kicad-jlcpcb-tools">https://github.com/harry10086/kicad-jlcpcb-tools</a></p><ul><li>汉化了大多数按钮；</li><li>从立创商城实时搜索元器件，会有一点延时，而且一次加载数量不能太多，国内网站反爬虫很厉害；</li><li>减少了元件过滤规则；</li><li>元件详情页可以复制；</li></ul><p><strong>KiCAD-JLCPCB-tools 国内版.zip 百度云链接:</strong> <a href="https://pan.baidu.com/s/1uk-77LpDFXMov0UyXq4dag?pwd=9sfg">https://pan.baidu.com/s/1uk-77LpDFXMov0UyXq4dag?pwd=9sfg</a><br>提取码: 9sfg</p><p><strong>KiCAD-JLCPCB-tools 国内版 夸克网盘链接：</strong><a href="https://pan.quark.cn/s/49af8e59def2">https://pan.quark.cn/s/49af8e59def2</a></p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/jlcpcb-tools-zh.png" alt="jlcpcb-tools-zh"></p><p>其他插件推荐：<a href="https://mianao.info/kicad-tool-recommend/">KiCad常用插件推荐</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在经过了 RC1,RC2 两个版本后，&lt;a href=&quot;https://www.kicad.org/blog/2026/03/Version-10.0.0-Released/&quot;&gt;KiCad 10.0.0 正式版终于发布了！&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;主要改进&quot;&gt;&lt;a </summary>
      
    
    
    
    <category term="Work" scheme="https://mianao.info/categories/Work/"/>
    
    
    <category term="EDA" scheme="https://mianao.info/tags/EDA/"/>
    
    <category term="KiCad" scheme="https://mianao.info/tags/KiCad/"/>
    
  </entry>
  
  <entry>
    <title>最好用的内外网穿透-Cloudflare Tunnels</title>
    <link href="https://mianao.info/best-cloudflare-tunnels-guide/"/>
    <id>https://mianao.info/best-cloudflare-tunnels-guide/</id>
    <published>2026-03-14T16:00:00.000Z</published>
    <updated>2026-03-14T16:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>内外网穿透方案多的是，开源的，商业的，比如：FRP，NPS，Tailscale，ZeroTier，花生壳，我也用 lucky，DDNS GO 搞过 IPv6。<br>现在总结一下，在没有公网 IP 的时候，最简单易用，最安全的还是 <strong>Cloudflare Tunnels</strong>。当然，如果有 IPv6 的使用环境，DDNS GO 更适合，速度更有保证。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/CloudflareTunnels.webp" alt="CloudflareTunnels"><br><strong>优点：</strong></p><ul><li>免费；</li><li>Cloudflare 出品，大厂保证安全；</li><li>安装简单，只需要在局域网内设备运行 Docker；</li><li>一个域名搞定内网所有设备访问，Tunnels 功能强大；<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/route2.webp" alt="route2"><br><strong>缺点：</strong></li><li>网络有的地方可能访问比较慢，有的时间可能不稳定；</li></ul><h3 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h3><ul><li>有一个域名，并且绑在 Cloudflare。</li><li>局域网内有一台可以长期运行 Docker 的设备。</li></ul><h3 id="新建-Cloudflare-Tunnels"><a href="#新建-Cloudflare-Tunnels" class="headerlink" title="新建 Cloudflare Tunnels"></a>新建 Cloudflare Tunnels</h3><ol><li><p>因为 Cloudflare 布局多次改变，我找了半天才找到 Tunnels。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/tunnel0.webp" alt="tunnel0"></p></li><li><p>新建一个隧道。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/tunnel1.webp" alt="tunnel1"></p></li><li><p>获得 Docker 指令和 key。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/tunnel2.webp" alt="tunnel2"></p></li></ol><h3 id="安装-Docker"><a href="#安装-Docker" class="headerlink" title="安装 Docker"></a>安装 Docker</h3><ol start="4"><li>安装 Docker。我是在群晖测试的，路由器也是一样的，无非就是命令+key。<br>在仓库搜索 Cloudflare 并下载镜像：<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/hub.webp" alt="hub"></li></ol><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/image.webp" alt="image"></p><ol start="5"><li><p>建立容器。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/container1.webp" alt="container1"></p></li><li><p>修改网络和命令。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/container2.webp" alt="container2"></p></li><li><p>运行 Docker。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/container3.webp" alt="container3"></p></li></ol><h3 id="配置-Cloudflare-Tunnels"><a href="#配置-Cloudflare-Tunnels" class="headerlink" title="配置 Cloudflare Tunnels"></a>配置 Cloudflare Tunnels</h3><ol start="8"><li><p>这时可以看到 Cloudflare Tunnels 连接状态：成功。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/tunnel3.webp" alt="tunnel3"></p></li><li><p>继续配置。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/tunnel4.webp" alt="tunnel4"></p></li><li><p>添加路由。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/route1.webp" alt="route1"></p></li><li><p>选择 published application。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/route2.webp" alt="route2"></p></li><li><p>选择域名，添加子域名，内网 IP 和端口。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/route3.webp" alt="route3"></p></li><li><p>route 可以配置多个，我不知道有没有限制，反正我加了十几个了。收工。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Cloudflaretunnels/route4.webp" alt="route4"></p></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;内外网穿透方案多的是，开源的，商业的，比如：FRP，NPS，Tailscale，ZeroTier，花生壳，我也用 lucky，DDNS GO 搞过 IPv6。&lt;br&gt;现在总结一下，在没有公网 IP 的时候，最简单易用，最安全的还是 &lt;strong&gt;Cloudflare Tu</summary>
      
    
    
    
    <category term="WEB" scheme="https://mianao.info/categories/WEB/"/>
    
    
    <category term="Cloudflare" scheme="https://mianao.info/tags/Cloudflare/"/>
    
  </entry>
  
  <entry>
    <title>乌龟自动喂食器再次升级</title>
    <link href="https://mianao.info/diy-turtle-auto-feeder-upgrade-again-cloudflare/"/>
    <id>https://mianao.info/diy-turtle-auto-feeder-upgrade-again-cloudflare/</id>
    <published>2026-02-19T16:00:00.000Z</published>
    <updated>2026-02-19T16:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>去年我升级了乌龟的自动喂食器：<a href="https://mianao.info/diy-turtle-auto-feeder-v2-upgrade/">乌龟自动喂食器2.0升级</a>，基于 Arduino cloud 的 APP 来远程管理，就发现有时候连不上，控制按钮免费版也最多使用五个，还有不知道是不是 ESP8266 模块问题，连 Wi-Fi 有时也断。趁着春节假期，换 ESP32，远程也改用 Cloudflare workers 来控制。</p><p>一个遗憾的消息是，去年年底的时候，一只最小的乌龟死了，之前一直烂脚，我隔三岔五的用药水涂脚，但最终还是没救了，不知道是不是因为烂脚死的。现在就只剩下两只了。</p><h3 id="硬件设计"><a href="#硬件设计" class="headerlink" title="硬件设计"></a>硬件设计</h3><p>主控换成了 ESP32 开发板。这板我也不知道啥时候买的了。不用模块一个是因为省钱，一个是比较方便我升级和调试固件，直接拔出来连到电脑就好了，以前还得把连接线拔了拆整个板。<br>另外把继电器换成了 PMOS，水位监测，喂食电机驱动和之前一样。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Autofeed/sch_V2.2.webp" alt="sch_V2"><br>电路设计我使用 KiCAD 9 了，3D 库也一起打包了。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Autofeed/Autofeed_V2.2.webp" alt="Autofeed_V2"></p><p>KiCAD 源文件百度云链接: <a href="https://pan.baidu.com/s/1gtLMKz2DZfkLErWBy8DLeQ?pwd=6duk">https://pan.baidu.com/s/1gtLMKz2DZfkLErWBy8DLeQ?pwd=6duk</a><br>提取码: 6duk</p><h3 id="软件设计"><a href="#软件设计" class="headerlink" title="软件设计"></a>软件设计</h3><p>软件设计还是用的 Arduino 框架，通过 Cloudflare Workers 远程控制，使用免费的 MQTT 服务通信（HiveMQ）,国内也可以选用 EMQX，同样有免费额度。</p><p>推荐一下 <strong>Google antigravity</strong>，opus 4.6,gemini 3.0 都有免费额度，今天已经有 gemini 3.1pro 了。<br>这种简单的软件设计很容易就搞定，bug 修复只要告诉 AI 详细信息，很快就修复了。</p><p>主要文件如下：</p><table><thead><tr><th>文件</th><th>说明</th></tr></thead><tbody><tr><td>Autofeed2.2.ino</td><td>ESP32 Arduino 固件</td></tr><tr><td>worker.js</td><td>Cloudflare Worker 控制面板</td></tr></tbody></table><h4 id="系统架构"><a href="#系统架构" class="headerlink" title="系统架构"></a>系统架构</h4><pre><code class="highlight mermaid">graph LR    A[&quot;Cloudflare Workers&lt;br/&gt;(Web 控制面板)&quot;] -- &quot;MQTT over WebSocket&quot; &lt;--&gt; B[&quot;HiveMQ Cloud Broker&quot;]    B -- &quot;MQTT over TLS&quot; &lt;--&gt; C[&quot;ESP32-WROOM-32D&quot;]    C --&gt; D[&quot;电机 (GPIO14/27)&quot;]    C --&gt; E[&quot;进水泵 (GPIO19)&quot;]    C --&gt; F[&quot;循环泵 (GPIO18)&quot;]    C --&gt; G[&quot;水位传感器 (GPIO16/17)&quot;]</code></pre><h4 id="Arduino-固件功能"><a href="#Arduino-固件功能" class="headerlink" title="Arduino 固件功能"></a>Arduino 固件功能</h4><ul><li><strong>WiFi</strong>:使用 ESPTouch SmartConfig 配网，凭据持久化自动重连</li><li><strong>NTP</strong>: 自动同步东八区时间</li><li><strong>MQTT</strong>: TLS 8883 连接 HiveMQ Cloud，订阅命令&#x2F;发布状态</li><li><strong>电机</strong>: GPIO14&#x2F;27 PWM 正反转控制，支持多时间点定时调度 + 间隔天数</li><li><strong>进水泵</strong>: GPIO19，由水位传感器自动控制（GPIO17 低水位 &#x2F; GPIO16 高水位），开启时间超过3分钟自动关闭所有水泵</li><li><strong>循环泵</strong>: GPIO18，可配置开&#x2F;关秒数循环工作</li><li><strong>持久化</strong>: 所有配置保存到 ESP32 Flash (Preferences)，断电不丢失</li><li><strong>状态上报</strong>: 每 10 秒通过 MQTT 上报完整设备状态</li></ul><h4 id="ESP32-Arduino-代码"><a href="#ESP32-Arduino-代码" class="headerlink" title="ESP32 Arduino 代码"></a>ESP32 Arduino 代码</h4><p>实现以下功能:</p><ol><li><p><strong>WiFi 连接 (ESPTouch&#x2F;SmartConfig)</strong></p><ul><li>使用 <code>WiFi.beginSmartConfig()</code> 等待配网</li><li>连接成功后保存并自动重连</li></ul></li><li><p><strong>NTP 时间同步</strong></p><ul><li><code>configTime()</code> 设置东八区 (UTC+8)，从网络获取当地时间</li></ul></li><li><p><strong>HiveMQ MQTT 连接</strong></p><ul><li>使用 <code>PubSubClient</code> 库连接 HiveMQ Cloud (TLS 8883)</li><li>订阅 <code>autofeed/cmd</code>，发布 <code>autofeed/status</code></li><li>接收命令后解析 JSON 更新本地配置</li></ul></li><li><p><strong>电机控制 (PWM)</strong></p><ul><li>GPIO14 (<code>pwm1Pin</code>) + GPIO27 (<code>pwm2Pin</code>)</li><li>正转: GPIO14 HIGH, GPIO27 LOW</li><li>反转: GPIO14 LOW, GPIO27 HIGH</li><li>停止: 两个都 LOW</li><li>根据 <code>runSeconds</code> 控制每次运行时长</li></ul></li><li><p><strong>电机调度器</strong></p><ul><li>支持 <code>intervalDays</code>（1&#x3D;每天, 2&#x3D;隔天, 3&#x3D;隔两天…）</li><li>支持一天内多个时间点执行</li><li>基于 NTP 时间判断是否触发</li></ul></li><li><p><strong>进水泵控制</strong></p><ul><li>GPIO19 (<code>pumpInPin</code>)</li><li>由水位传感器自动控制（开关打开时）</li><li>GPIO17 HIGH → 水位低 → 启动进水泵</li><li>GPIO16 HIGH → 水位高 → 停止进水泵</li></ul></li><li><p><strong>循环泵控制</strong></p><ul><li>GPIO18 (<code>pumpOutPin</code>)</li><li>循环工作模式: 开 N 秒 → 关 M 秒 → 循环</li></ul></li><li><p><strong>状态上报</strong></p><ul><li>每 10 秒通过 MQTT 上报一次设备状态</li></ul></li></ol><p><strong>Arduino 必须安装依赖库：</strong></p><ul><li><strong>PubSubClient</strong></li><li><strong>ArduinoJson</strong></li></ul><p>Arduino 代码如下：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br><span class="line">405</span><br><span class="line">406</span><br><span class="line">407</span><br><span class="line">408</span><br><span class="line">409</span><br><span class="line">410</span><br><span class="line">411</span><br><span class="line">412</span><br><span class="line">413</span><br><span class="line">414</span><br><span class="line">415</span><br><span class="line">416</span><br><span class="line">417</span><br><span class="line">418</span><br><span class="line">419</span><br><span class="line">420</span><br><span class="line">421</span><br><span class="line">422</span><br><span class="line">423</span><br><span class="line">424</span><br><span class="line">425</span><br><span class="line">426</span><br><span class="line">427</span><br><span class="line">428</span><br><span class="line">429</span><br><span class="line">430</span><br><span class="line">431</span><br><span class="line">432</span><br><span class="line">433</span><br><span class="line">434</span><br><span class="line">435</span><br><span class="line">436</span><br><span class="line">437</span><br><span class="line">438</span><br><span class="line">439</span><br><span class="line">440</span><br><span class="line">441</span><br><span class="line">442</span><br><span class="line">443</span><br><span class="line">444</span><br><span class="line">445</span><br><span class="line">446</span><br><span class="line">447</span><br><span class="line">448</span><br><span class="line">449</span><br><span class="line">450</span><br><span class="line">451</span><br><span class="line">452</span><br><span class="line">453</span><br><span class="line">454</span><br><span class="line">455</span><br><span class="line">456</span><br><span class="line">457</span><br><span class="line">458</span><br><span class="line">459</span><br><span class="line">460</span><br><span class="line">461</span><br><span class="line">462</span><br><span class="line">463</span><br><span class="line">464</span><br><span class="line">465</span><br><span class="line">466</span><br><span class="line">467</span><br><span class="line">468</span><br><span class="line">469</span><br><span class="line">470</span><br><span class="line">471</span><br><span class="line">472</span><br><span class="line">473</span><br><span class="line">474</span><br><span class="line">475</span><br><span class="line">476</span><br><span class="line">477</span><br><span class="line">478</span><br><span class="line">479</span><br><span class="line">480</span><br><span class="line">481</span><br><span class="line">482</span><br><span class="line">483</span><br><span class="line">484</span><br><span class="line">485</span><br><span class="line">486</span><br><span class="line">487</span><br><span class="line">488</span><br><span class="line">489</span><br><span class="line">490</span><br><span class="line">491</span><br><span class="line">492</span><br><span class="line">493</span><br><span class="line">494</span><br><span class="line">495</span><br><span class="line">496</span><br><span class="line">497</span><br><span class="line">498</span><br><span class="line">499</span><br><span class="line">500</span><br><span class="line">501</span><br><span class="line">502</span><br><span class="line">503</span><br><span class="line">504</span><br><span class="line">505</span><br><span class="line">506</span><br><span class="line">507</span><br><span class="line">508</span><br><span class="line">509</span><br><span class="line">510</span><br><span class="line">511</span><br><span class="line">512</span><br><span class="line">513</span><br><span class="line">514</span><br><span class="line">515</span><br><span class="line">516</span><br><span class="line">517</span><br><span class="line">518</span><br><span class="line">519</span><br><span class="line">520</span><br><span class="line">521</span><br><span class="line">522</span><br><span class="line">523</span><br><span class="line">524</span><br><span class="line">525</span><br><span class="line">526</span><br><span class="line">527</span><br><span class="line">528</span><br><span class="line">529</span><br><span class="line">530</span><br><span class="line">531</span><br><span class="line">532</span><br><span class="line">533</span><br><span class="line">534</span><br><span class="line">535</span><br><span class="line">536</span><br><span class="line">537</span><br><span class="line">538</span><br><span class="line">539</span><br><span class="line">540</span><br><span class="line">541</span><br><span class="line">542</span><br><span class="line">543</span><br><span class="line">544</span><br><span class="line">545</span><br><span class="line">546</span><br><span class="line">547</span><br><span class="line">548</span><br><span class="line">549</span><br><span class="line">550</span><br><span class="line">551</span><br><span class="line">552</span><br><span class="line">553</span><br><span class="line">554</span><br><span class="line">555</span><br><span class="line">556</span><br><span class="line">557</span><br><span class="line">558</span><br><span class="line">559</span><br><span class="line">560</span><br><span class="line">561</span><br><span class="line">562</span><br><span class="line">563</span><br><span class="line">564</span><br><span class="line">565</span><br><span class="line">566</span><br><span class="line">567</span><br><span class="line">568</span><br><span class="line">569</span><br><span class="line">570</span><br><span class="line">571</span><br><span class="line">572</span><br><span class="line">573</span><br><span class="line">574</span><br><span class="line">575</span><br><span class="line">576</span><br><span class="line">577</span><br><span class="line">578</span><br><span class="line">579</span><br><span class="line">580</span><br><span class="line">581</span><br><span class="line">582</span><br><span class="line">583</span><br><span class="line">584</span><br><span class="line">585</span><br><span class="line">586</span><br><span class="line">587</span><br><span class="line">588</span><br><span class="line">589</span><br><span class="line">590</span><br><span class="line">591</span><br><span class="line">592</span><br><span class="line">593</span><br><span class="line">594</span><br><span class="line">595</span><br><span class="line">596</span><br><span class="line">597</span><br><span class="line">598</span><br><span class="line">599</span><br><span class="line">600</span><br><span class="line">601</span><br><span class="line">602</span><br><span class="line">603</span><br><span class="line">604</span><br><span class="line">605</span><br><span class="line">606</span><br><span class="line">607</span><br><span class="line">608</span><br><span class="line">609</span><br><span class="line">610</span><br><span class="line">611</span><br><span class="line">612</span><br><span class="line">613</span><br><span class="line">614</span><br><span class="line">615</span><br><span class="line">616</span><br><span class="line">617</span><br><span class="line">618</span><br><span class="line">619</span><br><span class="line">620</span><br><span class="line">621</span><br><span class="line">622</span><br><span class="line">623</span><br><span class="line">624</span><br><span class="line">625</span><br><span class="line">626</span><br><span class="line">627</span><br><span class="line">628</span><br><span class="line">629</span><br><span class="line">630</span><br><span class="line">631</span><br><span class="line">632</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Autofeed 2.2 - ESP32 自动喂食水泵控制系统</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 硬件: ESP32-WROOM-32D</span></span><br><span class="line"><span class="comment"> * 通信: HiveMQ MQTT (TLS)</span></span><br><span class="line"><span class="comment"> * 配网: ESPTouch SmartConfig</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;ArduinoJson.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;Preferences.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;PubSubClient.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;WiFi.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;WiFiClientSecure.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== HiveMQ 配置 ====================</span></span><br><span class="line"><span class="comment">// HiveMQ Cloud Broker 地址</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MQTT_BROKER <span class="string">&quot;xxx.hivemq.cloud&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MQTT_PORT 8883               <span class="comment">// TLS 端口</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MQTT_USER <span class="string">&quot;name&quot;</span>            <span class="comment">// HiveMQ 用户名</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MQTT_PASS <span class="string">&quot;password&quot;</span>  <span class="comment">// HiveMQ 密码</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MQTT_CLIENT <span class="string">&quot;autofeed-esp32&quot;</span> <span class="comment">// 客户端 ID 自定</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== MQTT 主题 ====================</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> TOPIC_CMD <span class="string">&quot;autofeed/cmd&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> TOPIC_STATUS <span class="string">&quot;autofeed/status&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== 引脚定义 ====================</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PWM1_PIN 14       <span class="comment">// 电机 PWM1 — HIGH 时正转方向</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PWM2_PIN 27       <span class="comment">// 电机 PWM2 — HIGH 时反转方向</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PUMP_IN_PIN 19    <span class="comment">// 进水泵 — HIGH 工作</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PUMP_OUT_PIN 18   <span class="comment">// 循环泵 — HIGH 工作</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> WATER_LOW_PIN 17  <span class="comment">// 低水位传感器 — HIGH 表示水位低</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> WATER_HIGH_PIN 16 <span class="comment">// 高水位传感器 — HIGH 表示水位已满</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== NTP 配置 ====================</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> NTP_SERVER <span class="string">&quot;pool.ntp.org&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> GMT_OFFSET 28800 <span class="comment">// UTC+8 秒数</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> DST_OFFSET 0</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== 全局对象 ====================</span></span><br><span class="line">WiFiClientSecure espClient;</span><br><span class="line"><span class="function">PubSubClient <span class="title">mqtt</span><span class="params">(espClient)</span></span>;</span><br><span class="line">Preferences prefs;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== 电机配置 ====================</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">MotorConfig</span> &#123;</span><br><span class="line">  <span class="type">bool</span> enabled;</span><br><span class="line">  <span class="type">int</span> runSeconds;   <span class="comment">// 每次运行秒数</span></span><br><span class="line">  <span class="type">int</span> intervalDays; <span class="comment">// 间隔天数 (1=每天, 2=隔天, 3=隔两天...)</span></span><br><span class="line">  String times[<span class="number">10</span>]; <span class="comment">// 每天的执行时间列表 &quot;HH:MM&quot;</span></span><br><span class="line">  <span class="type">int</span> timeCount;    <span class="comment">// 时间点数量</span></span><br><span class="line">&#125; motorCfg = &#123;<span class="literal">false</span>, <span class="number">30</span>, <span class="number">1</span>, &#123;&#125;, <span class="number">0</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== 进水泵配置 ====================</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">PumpInConfig</span> &#123;</span><br><span class="line">  <span class="type">bool</span> enabled;</span><br><span class="line">&#125; pumpInCfg = &#123;<span class="literal">false</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== 循环泵配置 ====================</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">PumpOutConfig</span> &#123;</span><br><span class="line">  <span class="type">bool</span> enabled;</span><br><span class="line">  <span class="type">int</span> onSeconds;  <span class="comment">// 循环开启秒数</span></span><br><span class="line">  <span class="type">int</span> offSeconds; <span class="comment">// 循环关闭秒数</span></span><br><span class="line">&#125; pumpOutCfg = &#123;<span class="literal">false</span>, <span class="number">30</span>, <span class="number">30</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== 运行状态 ====================</span></span><br><span class="line"><span class="type">bool</span> motorRunning = <span class="literal">false</span>;</span><br><span class="line"><span class="type">unsigned</span> <span class="type">long</span> motorStartTime = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">bool</span> pumpInRunning = <span class="literal">false</span>;</span><br><span class="line"><span class="type">unsigned</span> <span class="type">long</span> pumpInStartTime = <span class="number">0</span>;</span><br><span class="line"><span class="type">bool</span> pumpOutRunning = <span class="literal">false</span>;</span><br><span class="line"><span class="type">unsigned</span> <span class="type">long</span> pumpOutCycleStart = <span class="number">0</span>;</span><br><span class="line"><span class="type">bool</span> pumpOutCycleState = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">bool</span> waterLow = <span class="literal">false</span>;</span><br><span class="line"><span class="type">bool</span> waterHigh = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== 定时器 ====================</span></span><br><span class="line"><span class="type">unsigned</span> <span class="type">long</span> lastStatusReport = <span class="number">0</span>;</span><br><span class="line"><span class="type">const</span> <span class="type">unsigned</span> <span class="type">long</span> STATUS_INTERVAL = <span class="number">10000</span>; <span class="comment">// 10 秒上报一次</span></span><br><span class="line"></span><br><span class="line"><span class="type">unsigned</span> <span class="type">long</span> lastScheduleCheck = <span class="number">0</span>;</span><br><span class="line"><span class="type">const</span> <span class="type">unsigned</span> <span class="type">long</span> SCHEDULE_INTERVAL = <span class="number">30000</span>; <span class="comment">// 30 秒检查一次调度</span></span><br><span class="line"></span><br><span class="line"><span class="type">unsigned</span> <span class="type">long</span> lastMqttReconnect = <span class="number">0</span>;</span><br><span class="line"><span class="type">const</span> <span class="type">unsigned</span> <span class="type">long</span> MQTT_RECONNECT_INTERVAL = <span class="number">5000</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 调度追踪: 记录今天是否已执行过各时间点</span></span><br><span class="line"><span class="type">bool</span> scheduledToday[<span class="number">10</span>] = &#123;<span class="literal">false</span>&#125;;</span><br><span class="line"><span class="type">int</span> lastCheckedDay = <span class="number">-1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== 函数前向声明 ====================</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">setupWiFi</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">setupMQTT</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">mqttCallback</span><span class="params">(<span class="type">char</span> *topic, byte *payload, <span class="type">unsigned</span> <span class="type">int</span> length)</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">reconnectMQTT</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">handleMotorSchedule</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">updateMotor</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">updatePumpIn</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">updatePumpOut</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">reportStatus</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">applyCommand</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *json)</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">saveConfig</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">loadConfig</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">stopMotor</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">startMotor</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== Setup ====================</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">setup</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  Serial.<span class="built_in">begin</span>(<span class="number">115200</span>);</span><br><span class="line">  Serial.<span class="built_in">println</span>(<span class="string">&quot;\n=== Autofeed 2.2 启动 ===&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 引脚初始化</span></span><br><span class="line">  <span class="built_in">pinMode</span>(PWM1_PIN, OUTPUT);</span><br><span class="line">  <span class="built_in">pinMode</span>(PWM2_PIN, OUTPUT);</span><br><span class="line">  <span class="built_in">pinMode</span>(PUMP_IN_PIN, OUTPUT);</span><br><span class="line">  <span class="built_in">pinMode</span>(PUMP_OUT_PIN, OUTPUT);</span><br><span class="line">  <span class="built_in">pinMode</span>(WATER_LOW_PIN, INPUT);</span><br><span class="line">  <span class="built_in">pinMode</span>(WATER_HIGH_PIN, INPUT);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 初始状态: 全部关闭</span></span><br><span class="line">  <span class="built_in">digitalWrite</span>(PWM1_PIN, LOW);</span><br><span class="line">  <span class="built_in">digitalWrite</span>(PWM2_PIN, LOW);</span><br><span class="line">  <span class="built_in">digitalWrite</span>(PUMP_IN_PIN, LOW);</span><br><span class="line">  <span class="built_in">digitalWrite</span>(PUMP_OUT_PIN, LOW);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 加载已保存的配置</span></span><br><span class="line">  <span class="built_in">loadConfig</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// WiFi 连接</span></span><br><span class="line">  <span class="built_in">setupWiFi</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// NTP 时间同步</span></span><br><span class="line">  <span class="built_in">configTime</span>(GMT_OFFSET, DST_OFFSET, NTP_SERVER);</span><br><span class="line">  Serial.<span class="built_in">println</span>(<span class="string">&quot;等待 NTP 时间同步...&quot;</span>);</span><br><span class="line">  <span class="keyword">struct</span> <span class="title class_">tm</span> timeinfo;</span><br><span class="line">  <span class="type">int</span> retries = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">while</span> (!<span class="built_in">getLocalTime</span>(&amp;timeinfo) &amp;&amp; retries &lt; <span class="number">20</span>) &#123;</span><br><span class="line">    <span class="built_in">delay</span>(<span class="number">500</span>);</span><br><span class="line">    Serial.<span class="built_in">print</span>(<span class="string">&quot;.&quot;</span>);</span><br><span class="line">    retries++;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (retries &lt; <span class="number">20</span>) &#123;</span><br><span class="line">    Serial.<span class="built_in">println</span>(<span class="string">&quot;\nNTP 同步成功!&quot;</span>);</span><br><span class="line">    Serial.<span class="built_in">printf</span>(<span class="string">&quot;当前时间: %04d-%02d-%02d %02d:%02d:%02d\n&quot;</span>,</span><br><span class="line">                  timeinfo.tm_year + <span class="number">1900</span>, timeinfo.tm_mon + <span class="number">1</span>,</span><br><span class="line">                  timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min,</span><br><span class="line">                  timeinfo.tm_sec);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    Serial.<span class="built_in">println</span>(<span class="string">&quot;\nNTP 同步失败, 将持续重试&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// MQTT 连接</span></span><br><span class="line">  <span class="built_in">setupMQTT</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== Loop ====================</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">loop</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 保持 WiFi 连接</span></span><br><span class="line">  <span class="keyword">if</span> (WiFi.<span class="built_in">status</span>() != WL_CONNECTED) &#123;</span><br><span class="line">    Serial.<span class="built_in">println</span>(<span class="string">&quot;WiFi 断开, 重新连接...&quot;</span>);</span><br><span class="line">    WiFi.<span class="built_in">reconnect</span>();</span><br><span class="line">    <span class="built_in">delay</span>(<span class="number">5000</span>);</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 保持 MQTT 连接</span></span><br><span class="line">  <span class="keyword">if</span> (!mqtt.<span class="built_in">connected</span>()) &#123;</span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> now = <span class="built_in">millis</span>();</span><br><span class="line">    <span class="keyword">if</span> (now - lastMqttReconnect &gt; MQTT_RECONNECT_INTERVAL) &#123;</span><br><span class="line">      lastMqttReconnect = now;</span><br><span class="line">      <span class="built_in">reconnectMQTT</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  mqtt.<span class="built_in">loop</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 读取水位传感器</span></span><br><span class="line">  waterLow = <span class="built_in">digitalRead</span>(WATER_LOW_PIN) == HIGH;</span><br><span class="line">  waterHigh = <span class="built_in">digitalRead</span>(WATER_HIGH_PIN) == HIGH;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 更新各模块</span></span><br><span class="line">  <span class="built_in">handleMotorSchedule</span>();</span><br><span class="line">  <span class="built_in">updateMotor</span>();</span><br><span class="line">  <span class="built_in">updatePumpIn</span>();</span><br><span class="line">  <span class="built_in">updatePumpOut</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 定时上报状态</span></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">long</span> now = <span class="built_in">millis</span>();</span><br><span class="line">  <span class="keyword">if</span> (now - lastStatusReport &gt;= STATUS_INTERVAL) &#123;</span><br><span class="line">    lastStatusReport = now;</span><br><span class="line">    <span class="built_in">reportStatus</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== WiFi SmartConfig ====================</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">setupWiFi</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  prefs.<span class="built_in">begin</span>(<span class="string">&quot;wifi&quot;</span>, <span class="literal">true</span>);</span><br><span class="line">  String ssid = prefs.<span class="built_in">getString</span>(<span class="string">&quot;ssid&quot;</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line">  String pass = prefs.<span class="built_in">getString</span>(<span class="string">&quot;pass&quot;</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line">  prefs.<span class="built_in">end</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (ssid.<span class="built_in">length</span>() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    Serial.<span class="built_in">printf</span>(<span class="string">&quot;尝试连接已保存的 WiFi: %s\n&quot;</span>, ssid.<span class="built_in">c_str</span>());</span><br><span class="line">    WiFi.<span class="built_in">begin</span>(ssid.<span class="built_in">c_str</span>(), pass.<span class="built_in">c_str</span>());</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> timeout = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span> (WiFi.<span class="built_in">status</span>() != WL_CONNECTED &amp;&amp; timeout &lt; <span class="number">20</span>) &#123;</span><br><span class="line">      <span class="built_in">delay</span>(<span class="number">500</span>);</span><br><span class="line">      Serial.<span class="built_in">print</span>(<span class="string">&quot;.&quot;</span>);</span><br><span class="line">      timeout++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (WiFi.<span class="built_in">status</span>() == WL_CONNECTED) &#123;</span><br><span class="line">      Serial.<span class="built_in">printf</span>(<span class="string">&quot;\nWiFi 已连接! IP: %s\n&quot;</span>,</span><br><span class="line">                    WiFi.<span class="built_in">localIP</span>().<span class="built_in">toString</span>().<span class="built_in">c_str</span>());</span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    Serial.<span class="built_in">println</span>(<span class="string">&quot;\n已保存的 WiFi 连接失败, 启动 SmartConfig...&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// SmartConfig 配网</span></span><br><span class="line">  WiFi.<span class="built_in">mode</span>(WIFI_STA);</span><br><span class="line">  WiFi.<span class="built_in">beginSmartConfig</span>(SC_TYPE_ESPTOUCH_V2);</span><br><span class="line">  Serial.<span class="built_in">println</span>(<span class="string">&quot;等待 SmartConfig 配网... (请使用 ESPTouch App)&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span> (!WiFi.<span class="built_in">smartConfigDone</span>()) &#123;</span><br><span class="line">    <span class="built_in">delay</span>(<span class="number">500</span>);</span><br><span class="line">    Serial.<span class="built_in">print</span>(<span class="string">&quot;.&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  Serial.<span class="built_in">println</span>(<span class="string">&quot;\nSmartConfig 配网完成!&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 等待连接</span></span><br><span class="line">  <span class="keyword">while</span> (WiFi.<span class="built_in">status</span>() != WL_CONNECTED) &#123;</span><br><span class="line">    <span class="built_in">delay</span>(<span class="number">500</span>);</span><br><span class="line">    Serial.<span class="built_in">print</span>(<span class="string">&quot;.&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  Serial.<span class="built_in">printf</span>(<span class="string">&quot;\nWiFi 已连接! IP: %s\n&quot;</span>, WiFi.<span class="built_in">localIP</span>().<span class="built_in">toString</span>().<span class="built_in">c_str</span>());</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 保存 WiFi 凭据</span></span><br><span class="line">  prefs.<span class="built_in">begin</span>(<span class="string">&quot;wifi&quot;</span>, <span class="literal">false</span>);</span><br><span class="line">  prefs.<span class="built_in">putString</span>(<span class="string">&quot;ssid&quot;</span>, WiFi.<span class="built_in">SSID</span>());</span><br><span class="line">  prefs.<span class="built_in">putString</span>(<span class="string">&quot;pass&quot;</span>, WiFi.<span class="built_in">psk</span>());</span><br><span class="line">  prefs.<span class="built_in">end</span>();</span><br><span class="line">  Serial.<span class="built_in">println</span>(<span class="string">&quot;WiFi 凭据已保存&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== MQTT 设置 ====================</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">setupMQTT</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  espClient.<span class="built_in">setInsecure</span>(); <span class="comment">// 跳过证书验证 (HiveMQ Cloud)</span></span><br><span class="line">  mqtt.<span class="built_in">setServer</span>(MQTT_BROKER, MQTT_PORT);</span><br><span class="line">  mqtt.<span class="built_in">setCallback</span>(mqttCallback);</span><br><span class="line">  mqtt.<span class="built_in">setBufferSize</span>(<span class="number">1024</span>);</span><br><span class="line">  <span class="built_in">reconnectMQTT</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">reconnectMQTT</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (mqtt.<span class="built_in">connected</span>())</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">  Serial.<span class="built_in">print</span>(<span class="string">&quot;连接 MQTT...&quot;</span>);</span><br><span class="line">  <span class="keyword">if</span> (mqtt.<span class="built_in">connect</span>(MQTT_CLIENT, MQTT_USER, MQTT_PASS)) &#123;</span><br><span class="line">    Serial.<span class="built_in">println</span>(<span class="string">&quot;成功!&quot;</span>);</span><br><span class="line">    mqtt.<span class="built_in">subscribe</span>(TOPIC_CMD);</span><br><span class="line">    Serial.<span class="built_in">printf</span>(<span class="string">&quot;已订阅: %s\n&quot;</span>, TOPIC_CMD);</span><br><span class="line">    <span class="comment">// 连接后立即上报一次状态</span></span><br><span class="line">    <span class="built_in">reportStatus</span>();</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    Serial.<span class="built_in">printf</span>(<span class="string">&quot;失败, rc=%d\n&quot;</span>, mqtt.<span class="built_in">state</span>());</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== MQTT 回调 ====================</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">mqttCallback</span><span class="params">(<span class="type">char</span> *topic, byte *payload, <span class="type">unsigned</span> <span class="type">int</span> length)</span> </span>&#123;</span><br><span class="line">  <span class="type">char</span> json[<span class="number">1024</span>];</span><br><span class="line">  <span class="keyword">if</span> (length &gt;= <span class="built_in">sizeof</span>(json))</span><br><span class="line">    length = <span class="built_in">sizeof</span>(json) - <span class="number">1</span>;</span><br><span class="line">  <span class="built_in">memcpy</span>(json, payload, length);</span><br><span class="line">  json[length] = <span class="string">&#x27;\0&#x27;</span>;</span><br><span class="line"></span><br><span class="line">  Serial.<span class="built_in">printf</span>(<span class="string">&quot;收到 [%s]: %s\n&quot;</span>, topic, json);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (<span class="built_in">String</span>(topic) == TOPIC_CMD) &#123;</span><br><span class="line">    <span class="built_in">applyCommand</span>(json);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== 解析并应用命令 ====================</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">applyCommand</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *json)</span> </span>&#123;</span><br><span class="line">  JsonDocument doc;</span><br><span class="line">  DeserializationError err = <span class="built_in">deserializeJson</span>(doc, json);</span><br><span class="line">  <span class="keyword">if</span> (err) &#123;</span><br><span class="line">    Serial.<span class="built_in">printf</span>(<span class="string">&quot;JSON 解析失败: %s\n&quot;</span>, err.<span class="built_in">c_str</span>());</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 电机配置</span></span><br><span class="line">  <span class="keyword">if</span> (doc.<span class="built_in">containsKey</span>(<span class="string">&quot;motor&quot;</span>)) &#123;</span><br><span class="line">    JsonObject motor = doc[<span class="string">&quot;motor&quot;</span>];</span><br><span class="line">    <span class="keyword">if</span> (motor.<span class="built_in">containsKey</span>(<span class="string">&quot;enabled&quot;</span>)) &#123;</span><br><span class="line">      motorCfg.enabled = motor[<span class="string">&quot;enabled&quot;</span>].<span class="built_in">as</span>&lt;<span class="type">bool</span>&gt;();</span><br><span class="line">      <span class="keyword">if</span> (!motorCfg.enabled) &#123;</span><br><span class="line">        <span class="built_in">stopMotor</span>();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (motor.<span class="built_in">containsKey</span>(<span class="string">&quot;runSeconds&quot;</span>)) &#123;</span><br><span class="line">      motorCfg.runSeconds = motor[<span class="string">&quot;runSeconds&quot;</span>].<span class="built_in">as</span>&lt;<span class="type">int</span>&gt;();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (motor.<span class="built_in">containsKey</span>(<span class="string">&quot;schedule&quot;</span>)) &#123;</span><br><span class="line">      JsonObject schedule = motor[<span class="string">&quot;schedule&quot;</span>];</span><br><span class="line">      <span class="keyword">if</span> (schedule.<span class="built_in">containsKey</span>(<span class="string">&quot;intervalDays&quot;</span>)) &#123;</span><br><span class="line">        motorCfg.intervalDays = schedule[<span class="string">&quot;intervalDays&quot;</span>].<span class="built_in">as</span>&lt;<span class="type">int</span>&gt;();</span><br><span class="line">        <span class="keyword">if</span> (motorCfg.intervalDays &lt; <span class="number">1</span>)</span><br><span class="line">          motorCfg.intervalDays = <span class="number">1</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> (schedule.<span class="built_in">containsKey</span>(<span class="string">&quot;times&quot;</span>)) &#123;</span><br><span class="line">        JsonArray times = schedule[<span class="string">&quot;times&quot;</span>];</span><br><span class="line">        motorCfg.timeCount = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; times.<span class="built_in">size</span>() &amp;&amp; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">          motorCfg.times[i] = times[i].<span class="built_in">as</span>&lt;String&gt;();</span><br><span class="line">          motorCfg.timeCount++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 重置今日调度状态，确保新修改/删除后的定时能正确重新匹配</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">          scheduledToday[i] = <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 进水泵配置</span></span><br><span class="line">  <span class="keyword">if</span> (doc.<span class="built_in">containsKey</span>(<span class="string">&quot;pumpIn&quot;</span>)) &#123;</span><br><span class="line">    JsonObject pumpIn = doc[<span class="string">&quot;pumpIn&quot;</span>];</span><br><span class="line">    <span class="keyword">if</span> (pumpIn.<span class="built_in">containsKey</span>(<span class="string">&quot;enabled&quot;</span>)) &#123;</span><br><span class="line">      pumpInCfg.enabled = pumpIn[<span class="string">&quot;enabled&quot;</span>].<span class="built_in">as</span>&lt;<span class="type">bool</span>&gt;();</span><br><span class="line">      <span class="keyword">if</span> (!pumpInCfg.enabled) &#123;</span><br><span class="line">        <span class="built_in">digitalWrite</span>(PUMP_IN_PIN, LOW);</span><br><span class="line">        pumpInRunning = <span class="literal">false</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 循环泵配置</span></span><br><span class="line">  <span class="keyword">if</span> (doc.<span class="built_in">containsKey</span>(<span class="string">&quot;pumpOut&quot;</span>)) &#123;</span><br><span class="line">    JsonObject pumpOut = doc[<span class="string">&quot;pumpOut&quot;</span>];</span><br><span class="line">    <span class="keyword">if</span> (pumpOut.<span class="built_in">containsKey</span>(<span class="string">&quot;enabled&quot;</span>)) &#123;</span><br><span class="line">      pumpOutCfg.enabled = pumpOut[<span class="string">&quot;enabled&quot;</span>].<span class="built_in">as</span>&lt;<span class="type">bool</span>&gt;();</span><br><span class="line">      <span class="keyword">if</span> (!pumpOutCfg.enabled) &#123;</span><br><span class="line">        <span class="built_in">digitalWrite</span>(PUMP_OUT_PIN, LOW);</span><br><span class="line">        pumpOutRunning = <span class="literal">false</span>;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 启动循环</span></span><br><span class="line">        pumpOutCycleStart = <span class="built_in">millis</span>();</span><br><span class="line">        pumpOutCycleState = <span class="literal">true</span>;</span><br><span class="line">        <span class="built_in">digitalWrite</span>(PUMP_OUT_PIN, HIGH);</span><br><span class="line">        pumpOutRunning = <span class="literal">true</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (pumpOut.<span class="built_in">containsKey</span>(<span class="string">&quot;onSeconds&quot;</span>)) &#123;</span><br><span class="line">      pumpOutCfg.onSeconds = pumpOut[<span class="string">&quot;onSeconds&quot;</span>].<span class="built_in">as</span>&lt;<span class="type">int</span>&gt;();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (pumpOut.<span class="built_in">containsKey</span>(<span class="string">&quot;offSeconds&quot;</span>)) &#123;</span><br><span class="line">      pumpOutCfg.offSeconds = pumpOut[<span class="string">&quot;offSeconds&quot;</span>].<span class="built_in">as</span>&lt;<span class="type">int</span>&gt;();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 保存配置到 Flash</span></span><br><span class="line">  <span class="built_in">saveConfig</span>();</span><br><span class="line">  Serial.<span class="built_in">println</span>(<span class="string">&quot;配置已更新并保存&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 更新后立即上报状态</span></span><br><span class="line">  <span class="built_in">reportStatus</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== 电机调度 ====================</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">handleMotorSchedule</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!motorCfg.enabled || motorCfg.timeCount == <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  <span class="keyword">if</span> (motorRunning)</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">long</span> now = <span class="built_in">millis</span>();</span><br><span class="line">  <span class="keyword">if</span> (now - lastScheduleCheck &lt; SCHEDULE_INTERVAL)</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  lastScheduleCheck = now;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">struct</span> <span class="title class_">tm</span> timeinfo;</span><br><span class="line">  <span class="keyword">if</span> (!<span class="built_in">getLocalTime</span>(&amp;timeinfo))</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">  <span class="type">int</span> today = timeinfo.tm_yday;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 新的一天: 重置调度状态</span></span><br><span class="line">  <span class="keyword">if</span> (today != lastCheckedDay) &#123;</span><br><span class="line">    lastCheckedDay = today;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">      scheduledToday[i] = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 检查间隔天数 (基于一年中的第几天)</span></span><br><span class="line">  <span class="comment">// intervalDays=1 每天, =2 隔天 (偶数天执行), =3 每隔两天...</span></span><br><span class="line">  <span class="keyword">if</span> (today % motorCfg.intervalDays != <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 检查是否到达预设时间</span></span><br><span class="line">  <span class="type">char</span> currentTime[<span class="number">6</span>];</span><br><span class="line">  <span class="built_in">snprintf</span>(currentTime, <span class="built_in">sizeof</span>(currentTime), <span class="string">&quot;%02d:%02d&quot;</span>, timeinfo.tm_hour,</span><br><span class="line">           timeinfo.tm_min);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; motorCfg.timeCount; i++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (scheduledToday[i])</span><br><span class="line">      <span class="keyword">continue</span>; <span class="comment">// 今天已执行过</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (motorCfg.times[i] == <span class="built_in">String</span>(currentTime)) &#123;</span><br><span class="line">      Serial.<span class="built_in">printf</span>(<span class="string">&quot;调度触发: %s\n&quot;</span>, currentTime);</span><br><span class="line">      scheduledToday[i] = <span class="literal">true</span>;</span><br><span class="line">      <span class="built_in">startMotor</span>();</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== 电机控制 ====================</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">startMotor</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (motorRunning)</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  Serial.<span class="built_in">println</span>(<span class="string">&quot;电机启动 (正转)&quot;</span>);</span><br><span class="line">  <span class="built_in">digitalWrite</span>(PWM1_PIN, HIGH);</span><br><span class="line">  <span class="built_in">digitalWrite</span>(PWM2_PIN, LOW);</span><br><span class="line">  motorRunning = <span class="literal">true</span>;</span><br><span class="line">  motorStartTime = <span class="built_in">millis</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">stopMotor</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  Serial.<span class="built_in">println</span>(<span class="string">&quot;电机停止&quot;</span>);</span><br><span class="line">  <span class="built_in">digitalWrite</span>(PWM1_PIN, LOW);</span><br><span class="line">  <span class="built_in">digitalWrite</span>(PWM2_PIN, LOW);</span><br><span class="line">  motorRunning = <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">updateMotor</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!motorRunning)</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">long</span> elapsed = <span class="built_in">millis</span>() - motorStartTime;</span><br><span class="line">  <span class="keyword">if</span> (elapsed &gt;= (<span class="type">unsigned</span> <span class="type">long</span>)motorCfg.runSeconds * <span class="number">1000UL</span>) &#123;</span><br><span class="line">    <span class="built_in">stopMotor</span>();</span><br><span class="line">    <span class="built_in">reportStatus</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== 进水泵控制 ====================</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">updatePumpIn</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!pumpInCfg.enabled) &#123;</span><br><span class="line">    <span class="keyword">if</span> (pumpInRunning) &#123;</span><br><span class="line">      <span class="built_in">digitalWrite</span>(PUMP_IN_PIN, LOW);</span><br><span class="line">      pumpInRunning = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 水位逻辑:</span></span><br><span class="line">  <span class="comment">// waterLow=true (GPIO17 HIGH) → 水位低 → 启动进水泵</span></span><br><span class="line">  <span class="comment">// waterHigh=true (GPIO16 HIGH) → 水位已满 → 停止进水泵</span></span><br><span class="line">  <span class="keyword">if</span> (waterHigh) &#123;</span><br><span class="line">    <span class="comment">// 水位到达最高, 停止进水</span></span><br><span class="line">    <span class="keyword">if</span> (pumpInRunning) &#123;</span><br><span class="line">      Serial.<span class="built_in">println</span>(<span class="string">&quot;水位已满, 进水泵停止&quot;</span>);</span><br><span class="line">      <span class="built_in">digitalWrite</span>(PUMP_IN_PIN, LOW);</span><br><span class="line">      pumpInRunning = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (waterLow) &#123;</span><br><span class="line">    <span class="comment">// 水位低, 启动进水</span></span><br><span class="line">    <span class="keyword">if</span> (!pumpInRunning) &#123;</span><br><span class="line">      Serial.<span class="built_in">println</span>(<span class="string">&quot;水位低, 进水泵启动&quot;</span>);</span><br><span class="line">      <span class="built_in">digitalWrite</span>(PUMP_IN_PIN, HIGH);</span><br><span class="line">      pumpInRunning = <span class="literal">true</span>;</span><br><span class="line">      pumpInStartTime = <span class="built_in">millis</span>(); <span class="comment">// 记录启动时间</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 进水泵运行超过 3 分钟开启保护</span></span><br><span class="line">  <span class="keyword">if</span> (pumpInRunning) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">millis</span>() - pumpInStartTime &gt; <span class="number">180000UL</span>) &#123; <span class="comment">// 180,000ms = 3分钟</span></span><br><span class="line">      Serial.<span class="built_in">println</span>(</span><br><span class="line">          <span class="string">&quot;!!! 警告: 进水超时(3分钟), 自动关闭并禁用[所有]水泵系统 !!!&quot;</span>);</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 关闭进水泵</span></span><br><span class="line">      <span class="built_in">digitalWrite</span>(PUMP_IN_PIN, LOW);</span><br><span class="line">      pumpInRunning = <span class="literal">false</span>;</span><br><span class="line">      pumpInCfg.enabled = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 同步关闭并禁用循环泵</span></span><br><span class="line">      <span class="built_in">digitalWrite</span>(PUMP_OUT_PIN, LOW);</span><br><span class="line">      pumpOutRunning = <span class="literal">false</span>;</span><br><span class="line">      pumpOutCfg.enabled = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">      <span class="built_in">saveConfig</span>();   <span class="comment">// 持久化这两个状态</span></span><br><span class="line">      <span class="built_in">reportStatus</span>(); <span class="comment">// 立即上报异常状态</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== 循环泵控制 ====================</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">updatePumpOut</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!pumpOutCfg.enabled) &#123;</span><br><span class="line">    <span class="keyword">if</span> (pumpOutRunning) &#123;</span><br><span class="line">      <span class="built_in">digitalWrite</span>(PUMP_OUT_PIN, LOW);</span><br><span class="line">      pumpOutRunning = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="type">unsigned</span> <span class="type">long</span> now = <span class="built_in">millis</span>();</span><br><span class="line">  <span class="type">unsigned</span> <span class="type">long</span> elapsed = now - pumpOutCycleStart;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (pumpOutCycleState) &#123;</span><br><span class="line">    <span class="comment">// 开启阶段</span></span><br><span class="line">    <span class="keyword">if</span> (elapsed &gt;= (<span class="type">unsigned</span> <span class="type">long</span>)pumpOutCfg.onSeconds * <span class="number">1000UL</span>) &#123;</span><br><span class="line">      <span class="comment">// 切换到关闭阶段</span></span><br><span class="line">      pumpOutCycleState = <span class="literal">false</span>;</span><br><span class="line">      pumpOutCycleStart = now;</span><br><span class="line">      <span class="built_in">digitalWrite</span>(PUMP_OUT_PIN, LOW);</span><br><span class="line">      pumpOutRunning = <span class="literal">false</span>;</span><br><span class="line">      Serial.<span class="built_in">println</span>(<span class="string">&quot;循环泵: 关闭&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 关闭阶段</span></span><br><span class="line">    <span class="keyword">if</span> (elapsed &gt;= (<span class="type">unsigned</span> <span class="type">long</span>)pumpOutCfg.offSeconds * <span class="number">1000UL</span>) &#123;</span><br><span class="line">      <span class="comment">// 切换到开启阶段</span></span><br><span class="line">      pumpOutCycleState = <span class="literal">true</span>;</span><br><span class="line">      pumpOutCycleStart = now;</span><br><span class="line">      <span class="built_in">digitalWrite</span>(PUMP_OUT_PIN, HIGH);</span><br><span class="line">      pumpOutRunning = <span class="literal">true</span>;</span><br><span class="line">      Serial.<span class="built_in">println</span>(<span class="string">&quot;循环泵: 开启&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== 上报状态 ====================</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">reportStatus</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!mqtt.<span class="built_in">connected</span>())</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">struct</span> <span class="title class_">tm</span> timeinfo;</span><br><span class="line">  <span class="type">char</span> timeStr[<span class="number">30</span>] = <span class="string">&quot;N/A&quot;</span>;</span><br><span class="line">  <span class="keyword">if</span> (<span class="built_in">getLocalTime</span>(&amp;timeinfo)) &#123;</span><br><span class="line">    <span class="built_in">strftime</span>(timeStr, <span class="built_in">sizeof</span>(timeStr), <span class="string">&quot;%Y-%m-%dT%H:%M:%S+08:00&quot;</span>, &amp;timeinfo);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  JsonDocument doc;</span><br><span class="line"></span><br><span class="line">  JsonObject motor = doc[<span class="string">&quot;motor&quot;</span>].<span class="built_in">to</span>&lt;JsonObject&gt;();</span><br><span class="line">  motor[<span class="string">&quot;enabled&quot;</span>] = motorCfg.enabled;</span><br><span class="line">  motor[<span class="string">&quot;running&quot;</span>] = motorRunning;</span><br><span class="line">  motor[<span class="string">&quot;runSeconds&quot;</span>] = motorCfg.runSeconds;</span><br><span class="line">  JsonObject schedule = motor[<span class="string">&quot;schedule&quot;</span>].<span class="built_in">to</span>&lt;JsonObject&gt;();</span><br><span class="line">  schedule[<span class="string">&quot;intervalDays&quot;</span>] = motorCfg.intervalDays;</span><br><span class="line">  JsonArray times = schedule[<span class="string">&quot;times&quot;</span>].<span class="built_in">to</span>&lt;JsonArray&gt;();</span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; motorCfg.timeCount; i++) &#123;</span><br><span class="line">    times.<span class="built_in">add</span>(motorCfg.times[i]);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  JsonObject pumpIn = doc[<span class="string">&quot;pumpIn&quot;</span>].<span class="built_in">to</span>&lt;JsonObject&gt;();</span><br><span class="line">  pumpIn[<span class="string">&quot;enabled&quot;</span>] = pumpInCfg.enabled;</span><br><span class="line">  pumpIn[<span class="string">&quot;running&quot;</span>] = pumpInRunning;</span><br><span class="line"></span><br><span class="line">  JsonObject pumpOut = doc[<span class="string">&quot;pumpOut&quot;</span>].<span class="built_in">to</span>&lt;JsonObject&gt;();</span><br><span class="line">  pumpOut[<span class="string">&quot;enabled&quot;</span>] = pumpOutCfg.enabled;</span><br><span class="line">  pumpOut[<span class="string">&quot;running&quot;</span>] = pumpOutRunning;</span><br><span class="line">  pumpOut[<span class="string">&quot;onSeconds&quot;</span>] = pumpOutCfg.onSeconds;</span><br><span class="line">  pumpOut[<span class="string">&quot;offSeconds&quot;</span>] = pumpOutCfg.offSeconds;</span><br><span class="line"></span><br><span class="line">  doc[<span class="string">&quot;waterLow&quot;</span>] = waterLow;</span><br><span class="line">  doc[<span class="string">&quot;waterHigh&quot;</span>] = waterHigh;</span><br><span class="line">  doc[<span class="string">&quot;time&quot;</span>] = timeStr;</span><br><span class="line">  doc[<span class="string">&quot;wifi&quot;</span>] = (WiFi.<span class="built_in">status</span>() == WL_CONNECTED);</span><br><span class="line"></span><br><span class="line">  <span class="type">char</span> buf[<span class="number">512</span>];</span><br><span class="line">  <span class="built_in">serializeJson</span>(doc, buf, <span class="built_in">sizeof</span>(buf));</span><br><span class="line">  mqtt.<span class="built_in">publish</span>(TOPIC_STATUS, buf);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ==================== 配置持久化 ====================</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">saveConfig</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  prefs.<span class="built_in">begin</span>(<span class="string">&quot;config&quot;</span>, <span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">  prefs.<span class="built_in">putBool</span>(<span class="string">&quot;mEnabled&quot;</span>, motorCfg.enabled);</span><br><span class="line">  prefs.<span class="built_in">putInt</span>(<span class="string">&quot;mRunSec&quot;</span>, motorCfg.runSeconds);</span><br><span class="line">  prefs.<span class="built_in">putInt</span>(<span class="string">&quot;mIntDays&quot;</span>, motorCfg.intervalDays);</span><br><span class="line">  prefs.<span class="built_in">putInt</span>(<span class="string">&quot;mTimeCnt&quot;</span>, motorCfg.timeCount);</span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; motorCfg.timeCount; i++) &#123;</span><br><span class="line">    <span class="type">char</span> key[<span class="number">8</span>];</span><br><span class="line">    <span class="built_in">snprintf</span>(key, <span class="built_in">sizeof</span>(key), <span class="string">&quot;mT%d&quot;</span>, i);</span><br><span class="line">    prefs.<span class="built_in">putString</span>(key, motorCfg.times[i]);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  prefs.<span class="built_in">putBool</span>(<span class="string">&quot;piEnabled&quot;</span>, pumpInCfg.enabled);</span><br><span class="line"></span><br><span class="line">  prefs.<span class="built_in">putBool</span>(<span class="string">&quot;poEnabled&quot;</span>, pumpOutCfg.enabled);</span><br><span class="line">  prefs.<span class="built_in">putInt</span>(<span class="string">&quot;poOnSec&quot;</span>, pumpOutCfg.onSeconds);</span><br><span class="line">  prefs.<span class="built_in">putInt</span>(<span class="string">&quot;poOffSec&quot;</span>, pumpOutCfg.offSeconds);</span><br><span class="line"></span><br><span class="line">  prefs.<span class="built_in">end</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">loadConfig</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  prefs.<span class="built_in">begin</span>(<span class="string">&quot;config&quot;</span>, <span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">  motorCfg.enabled = prefs.<span class="built_in">getBool</span>(<span class="string">&quot;mEnabled&quot;</span>, <span class="literal">false</span>);</span><br><span class="line">  motorCfg.runSeconds = prefs.<span class="built_in">getInt</span>(<span class="string">&quot;mRunSec&quot;</span>, <span class="number">30</span>);</span><br><span class="line">  motorCfg.intervalDays = prefs.<span class="built_in">getInt</span>(<span class="string">&quot;mIntDays&quot;</span>, <span class="number">1</span>);</span><br><span class="line">  motorCfg.timeCount = prefs.<span class="built_in">getInt</span>(<span class="string">&quot;mTimeCnt&quot;</span>, <span class="number">0</span>);</span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; motorCfg.timeCount &amp;&amp; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">    <span class="type">char</span> key[<span class="number">8</span>];</span><br><span class="line">    <span class="built_in">snprintf</span>(key, <span class="built_in">sizeof</span>(key), <span class="string">&quot;mT%d&quot;</span>, i);</span><br><span class="line">    motorCfg.times[i] = prefs.<span class="built_in">getString</span>(key, <span class="string">&quot;&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  pumpInCfg.enabled = prefs.<span class="built_in">getBool</span>(<span class="string">&quot;piEnabled&quot;</span>, <span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">  pumpOutCfg.enabled = prefs.<span class="built_in">getBool</span>(<span class="string">&quot;poEnabled&quot;</span>, <span class="literal">false</span>);</span><br><span class="line">  pumpOutCfg.onSeconds = prefs.<span class="built_in">getInt</span>(<span class="string">&quot;poOnSec&quot;</span>, <span class="number">30</span>);</span><br><span class="line">  pumpOutCfg.offSeconds = prefs.<span class="built_in">getInt</span>(<span class="string">&quot;poOffSec&quot;</span>, <span class="number">30</span>);</span><br><span class="line"></span><br><span class="line">  prefs.<span class="built_in">end</span>();</span><br><span class="line">  Serial.<span class="built_in">println</span>(<span class="string">&quot;配置已从 Flash 加载&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="Cloudflare-Workers-控制面板"><a href="#Cloudflare-Workers-控制面板" class="headerlink" title="Cloudflare Workers 控制面板"></a>Cloudflare Workers 控制面板</h4><p>js 单文件，内含完整 HTML&#x2F;CSS&#x2F;JS :</p><ol><li><p><strong>Web 界面</strong> — 控制面板 UI</p><ul><li>电机控制区：开关、运行秒数、调度设置（间隔天数 + 多时间点）</li><li>进水泵控制区：开关</li><li>循环泵控制区：开关、开启秒数、关闭秒数</li><li>设备状态区：实时显示各组件运行状态、水位、时间</li></ul></li><li><p><strong>MQTT 通信</strong> — 浏览器端通过 WebSocket 连接 HiveMQ</p><ul><li>使用 <code>mqtt.js</code> CDN 版在浏览器内直连 HiveMQ WebSocket 端口 (8884)</li><li>发送命令到 <code>autofeed/cmd</code></li><li>订阅 <code>autofeed/status</code> 实时显示状态</li></ul></li><li><p><strong>Worker 路由</strong> — <code>fetch</code> handler 返回 HTML 页面</p></li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br><span class="line">405</span><br><span class="line">406</span><br><span class="line">407</span><br><span class="line">408</span><br><span class="line">409</span><br><span class="line">410</span><br><span class="line">411</span><br><span class="line">412</span><br><span class="line">413</span><br><span class="line">414</span><br><span class="line">415</span><br><span class="line">416</span><br><span class="line">417</span><br><span class="line">418</span><br><span class="line">419</span><br><span class="line">420</span><br><span class="line">421</span><br><span class="line">422</span><br><span class="line">423</span><br><span class="line">424</span><br><span class="line">425</span><br><span class="line">426</span><br><span class="line">427</span><br><span class="line">428</span><br><span class="line">429</span><br><span class="line">430</span><br><span class="line">431</span><br><span class="line">432</span><br><span class="line">433</span><br><span class="line">434</span><br><span class="line">435</span><br><span class="line">436</span><br><span class="line">437</span><br><span class="line">438</span><br><span class="line">439</span><br><span class="line">440</span><br><span class="line">441</span><br><span class="line">442</span><br><span class="line">443</span><br><span class="line">444</span><br><span class="line">445</span><br><span class="line">446</span><br><span class="line">447</span><br><span class="line">448</span><br><span class="line">449</span><br><span class="line">450</span><br><span class="line">451</span><br><span class="line">452</span><br><span class="line">453</span><br><span class="line">454</span><br><span class="line">455</span><br><span class="line">456</span><br><span class="line">457</span><br><span class="line">458</span><br><span class="line">459</span><br><span class="line">460</span><br><span class="line">461</span><br><span class="line">462</span><br><span class="line">463</span><br><span class="line">464</span><br><span class="line">465</span><br><span class="line">466</span><br><span class="line">467</span><br><span class="line">468</span><br><span class="line">469</span><br><span class="line">470</span><br><span class="line">471</span><br><span class="line">472</span><br><span class="line">473</span><br><span class="line">474</span><br><span class="line">475</span><br><span class="line">476</span><br><span class="line">477</span><br><span class="line">478</span><br><span class="line">479</span><br><span class="line">480</span><br><span class="line">481</span><br><span class="line">482</span><br><span class="line">483</span><br><span class="line">484</span><br><span class="line">485</span><br><span class="line">486</span><br><span class="line">487</span><br><span class="line">488</span><br><span class="line">489</span><br><span class="line">490</span><br><span class="line">491</span><br><span class="line">492</span><br><span class="line">493</span><br><span class="line">494</span><br><span class="line">495</span><br><span class="line">496</span><br><span class="line">497</span><br><span class="line">498</span><br><span class="line">499</span><br><span class="line">500</span><br><span class="line">501</span><br><span class="line">502</span><br><span class="line">503</span><br><span class="line">504</span><br><span class="line">505</span><br><span class="line">506</span><br><span class="line">507</span><br><span class="line">508</span><br><span class="line">509</span><br><span class="line">510</span><br><span class="line">511</span><br><span class="line">512</span><br><span class="line">513</span><br><span class="line">514</span><br><span class="line">515</span><br><span class="line">516</span><br><span class="line">517</span><br><span class="line">518</span><br><span class="line">519</span><br><span class="line">520</span><br><span class="line">521</span><br><span class="line">522</span><br><span class="line">523</span><br><span class="line">524</span><br><span class="line">525</span><br><span class="line">526</span><br><span class="line">527</span><br><span class="line">528</span><br><span class="line">529</span><br><span class="line">530</span><br><span class="line">531</span><br><span class="line">532</span><br><span class="line">533</span><br><span class="line">534</span><br><span class="line">535</span><br><span class="line">536</span><br><span class="line">537</span><br><span class="line">538</span><br><span class="line">539</span><br><span class="line">540</span><br><span class="line">541</span><br><span class="line">542</span><br><span class="line">543</span><br><span class="line">544</span><br><span class="line">545</span><br><span class="line">546</span><br><span class="line">547</span><br><span class="line">548</span><br><span class="line">549</span><br><span class="line">550</span><br><span class="line">551</span><br><span class="line">552</span><br><span class="line">553</span><br><span class="line">554</span><br><span class="line">555</span><br><span class="line">556</span><br><span class="line">557</span><br><span class="line">558</span><br><span class="line">559</span><br><span class="line">560</span><br><span class="line">561</span><br><span class="line">562</span><br><span class="line">563</span><br><span class="line">564</span><br><span class="line">565</span><br><span class="line">566</span><br><span class="line">567</span><br><span class="line">568</span><br><span class="line">569</span><br><span class="line">570</span><br><span class="line">571</span><br><span class="line">572</span><br><span class="line">573</span><br><span class="line">574</span><br><span class="line">575</span><br><span class="line">576</span><br><span class="line">577</span><br><span class="line">578</span><br><span class="line">579</span><br><span class="line">580</span><br><span class="line">581</span><br><span class="line">582</span><br><span class="line">583</span><br><span class="line">584</span><br><span class="line">585</span><br><span class="line">586</span><br><span class="line">587</span><br><span class="line">588</span><br><span class="line">589</span><br><span class="line">590</span><br><span class="line">591</span><br><span class="line">592</span><br><span class="line">593</span><br><span class="line">594</span><br><span class="line">595</span><br><span class="line">596</span><br><span class="line">597</span><br><span class="line">598</span><br><span class="line">599</span><br><span class="line">600</span><br><span class="line">601</span><br><span class="line">602</span><br><span class="line">603</span><br><span class="line">604</span><br><span class="line">605</span><br><span class="line">606</span><br><span class="line">607</span><br><span class="line">608</span><br><span class="line">609</span><br><span class="line">610</span><br><span class="line">611</span><br><span class="line">612</span><br><span class="line">613</span><br><span class="line">614</span><br><span class="line">615</span><br><span class="line">616</span><br><span class="line">617</span><br><span class="line">618</span><br><span class="line">619</span><br><span class="line">620</span><br><span class="line">621</span><br><span class="line">622</span><br><span class="line">623</span><br><span class="line">624</span><br><span class="line">625</span><br><span class="line">626</span><br><span class="line">627</span><br><span class="line">628</span><br><span class="line">629</span><br><span class="line">630</span><br><span class="line">631</span><br><span class="line">632</span><br><span class="line">633</span><br><span class="line">634</span><br><span class="line">635</span><br><span class="line">636</span><br><span class="line">637</span><br><span class="line">638</span><br><span class="line">639</span><br><span class="line">640</span><br><span class="line">641</span><br><span class="line">642</span><br><span class="line">643</span><br><span class="line">644</span><br><span class="line">645</span><br><span class="line">646</span><br><span class="line">647</span><br><span class="line">648</span><br><span class="line">649</span><br><span class="line">650</span><br><span class="line">651</span><br><span class="line">652</span><br><span class="line">653</span><br><span class="line">654</span><br><span class="line">655</span><br><span class="line">656</span><br><span class="line">657</span><br><span class="line">658</span><br><span class="line">659</span><br><span class="line">660</span><br><span class="line">661</span><br><span class="line">662</span><br><span class="line">663</span><br><span class="line">664</span><br><span class="line">665</span><br><span class="line">666</span><br><span class="line">667</span><br><span class="line">668</span><br><span class="line">669</span><br><span class="line">670</span><br><span class="line">671</span><br><span class="line">672</span><br><span class="line">673</span><br><span class="line">674</span><br><span class="line">675</span><br><span class="line">676</span><br><span class="line">677</span><br><span class="line">678</span><br><span class="line">679</span><br><span class="line">680</span><br><span class="line">681</span><br><span class="line">682</span><br><span class="line">683</span><br><span class="line">684</span><br><span class="line">685</span><br><span class="line">686</span><br><span class="line">687</span><br><span class="line">688</span><br><span class="line">689</span><br><span class="line">690</span><br><span class="line">691</span><br><span class="line">692</span><br><span class="line">693</span><br><span class="line">694</span><br><span class="line">695</span><br><span class="line">696</span><br><span class="line">697</span><br><span class="line">698</span><br><span class="line">699</span><br><span class="line">700</span><br><span class="line">701</span><br><span class="line">702</span><br><span class="line">703</span><br><span class="line">704</span><br><span class="line">705</span><br><span class="line">706</span><br><span class="line">707</span><br><span class="line">708</span><br><span class="line">709</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Autofeed 2.2 — Cloudflare Worker 控制面板</span></span><br><span class="line"><span class="comment">// 部署方式:命令 `wrangler deploy` 或在 Cloudflare Dashboard 中粘贴此代码</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="keyword">async</span> <span class="title function_">fetch</span>(<span class="params">request</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Response</span>(<span class="variable constant_">HTML_CONTENT</span>, &#123;</span><br><span class="line">      <span class="attr">headers</span>: &#123; <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;text/html; charset=utf-8&#x27;</span> &#125;,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">HTML_CONTENT</span> = <span class="string">`&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="string">&lt;html lang=&quot;zh-CN&quot;&gt;</span></span><br><span class="line"><span class="string">&lt;head&gt;</span></span><br><span class="line"><span class="string">&lt;meta charset=&quot;UTF-8&quot;&gt;</span></span><br><span class="line"><span class="string">&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;</span></span><br><span class="line"><span class="string">&lt;link rel=&quot;icon&quot; href=&quot;https://fav.farm/🐢&quot; /&gt;</span></span><br><span class="line"><span class="string">&lt;title&gt;Autofeed 控制面板&lt;/title&gt;</span></span><br><span class="line"><span class="string">&lt;meta name=&quot;description&quot; content=&quot;ESP32 远程控制面板&quot;&gt;</span></span><br><span class="line"><span class="string">&lt;link rel=&quot;preconnect&quot; href=&quot;https://googlefonts.mirrors.sjtug.sjtu.edu.cn&quot;&gt;</span></span><br><span class="line"><span class="string">&lt;link href=&quot;https://googlefonts.mirrors.sjtug.sjtu.edu.cn/css2?family=Inter:wght@400;500;600;700&amp;display=swap&quot; rel=&quot;stylesheet&quot;&gt;</span></span><br><span class="line"><span class="string">&lt;script src=&quot;https://unpkg.com/mqtt@5/dist/mqtt.min.js&quot;&gt;&lt;/script&gt;</span></span><br><span class="line"><span class="string">&lt;style&gt;</span></span><br><span class="line"><span class="string">  :root &#123;</span></span><br><span class="line"><span class="string">    --bg: #0f1117;</span></span><br><span class="line"><span class="string">    --surface: #1a1d27;</span></span><br><span class="line"><span class="string">    --surface2: #242836;</span></span><br><span class="line"><span class="string">    --border: #2e3348;</span></span><br><span class="line"><span class="string">    --text: #e4e6f0;</span></span><br><span class="line"><span class="string">    --text2: #8b8fa8;</span></span><br><span class="line"><span class="string">    --accent: #6c5ce7;</span></span><br><span class="line"><span class="string">    --accent-glow: rgba(108,92,231,0.3);</span></span><br><span class="line"><span class="string">    --green: #00b894;</span></span><br><span class="line"><span class="string">    --green-glow: rgba(0,184,148,0.3);</span></span><br><span class="line"><span class="string">    --red: #e17055;</span></span><br><span class="line"><span class="string">    --red-glow: rgba(225,112,85,0.3);</span></span><br><span class="line"><span class="string">    --blue: #0984e3;</span></span><br><span class="line"><span class="string">    --blue-glow: rgba(9,132,227,0.3);</span></span><br><span class="line"><span class="string">    --yellow: #fdcb6e;</span></span><br><span class="line"><span class="string">    --radius: 16px;</span></span><br><span class="line"><span class="string">    --radius-sm: 10px;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  * &#123; margin: 0; padding: 0; box-sizing: border-box; &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  body &#123;</span></span><br><span class="line"><span class="string">    font-family: &#x27;Inter&#x27;, -apple-system, sans-serif;</span></span><br><span class="line"><span class="string">    background: var(--bg);</span></span><br><span class="line"><span class="string">    color: var(--text);</span></span><br><span class="line"><span class="string">    min-height: 100vh;</span></span><br><span class="line"><span class="string">    padding: 20px;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  .container &#123;</span></span><br><span class="line"><span class="string">    max-width: 680px;</span></span><br><span class="line"><span class="string">    margin: 0 auto;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  /* Header */</span></span><br><span class="line"><span class="string">  .header &#123;</span></span><br><span class="line"><span class="string">    text-align: center;</span></span><br><span class="line"><span class="string">    padding: 30px 0 20px;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .header h1 &#123;</span></span><br><span class="line"><span class="string">    font-size: 1.8rem;</span></span><br><span class="line"><span class="string">    font-weight: 700;</span></span><br><span class="line"><span class="string">    margin-bottom: 8px;</span></span><br><span class="line"><span class="string">    display: flex;</span></span><br><span class="line"><span class="string">    align-items: center;</span></span><br><span class="line"><span class="string">    justify-content: center;</span></span><br><span class="line"><span class="string">    gap: 8px;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .header h1 span &#123;</span></span><br><span class="line"><span class="string">    background: linear-gradient(135deg, #6c5ce7, #a29bfe);</span></span><br><span class="line"><span class="string">    -webkit-background-clip: text;</span></span><br><span class="line"><span class="string">    -webkit-text-fill-color: transparent;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .status-badge &#123;</span></span><br><span class="line"><span class="string">    display: inline-flex;</span></span><br><span class="line"><span class="string">    align-items: center;</span></span><br><span class="line"><span class="string">    gap: 6px;</span></span><br><span class="line"><span class="string">    padding: 6px 14px;</span></span><br><span class="line"><span class="string">    border-radius: 20px;</span></span><br><span class="line"><span class="string">    font-size: 0.8rem;</span></span><br><span class="line"><span class="string">    font-weight: 500;</span></span><br><span class="line"><span class="string">    background: var(--surface);</span></span><br><span class="line"><span class="string">    border: 1px solid var(--border);</span></span><br><span class="line"><span class="string">    transition: all 0.3s;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .status-badge.connected &#123;</span></span><br><span class="line"><span class="string">    border-color: var(--green);</span></span><br><span class="line"><span class="string">    box-shadow: 0 0 12px var(--green-glow);</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .status-badge.disconnected &#123;</span></span><br><span class="line"><span class="string">    border-color: var(--red);</span></span><br><span class="line"><span class="string">    box-shadow: 0 0 12px var(--red-glow);</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .status-dot &#123;</span></span><br><span class="line"><span class="string">    width: 8px; height: 8px;</span></span><br><span class="line"><span class="string">    border-radius: 50%;</span></span><br><span class="line"><span class="string">    background: var(--red);</span></span><br><span class="line"><span class="string">    transition: background 0.3s;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .status-badge.connected .status-dot &#123; background: var(--green); &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  /* Cards */</span></span><br><span class="line"><span class="string">  .card &#123;</span></span><br><span class="line"><span class="string">    background: var(--surface);</span></span><br><span class="line"><span class="string">    border: 1px solid var(--border);</span></span><br><span class="line"><span class="string">    border-radius: var(--radius);</span></span><br><span class="line"><span class="string">    padding: 24px;</span></span><br><span class="line"><span class="string">    margin-bottom: 16px;</span></span><br><span class="line"><span class="string">    transition: border-color 0.3s, box-shadow 0.3s;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .card:hover &#123;</span></span><br><span class="line"><span class="string">    border-color: rgba(108,92,231,0.4);</span></span><br><span class="line"><span class="string">    box-shadow: 0 4px 24px rgba(0,0,0,0.3);</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .card-header &#123;</span></span><br><span class="line"><span class="string">    display: flex;</span></span><br><span class="line"><span class="string">    justify-content: space-between;</span></span><br><span class="line"><span class="string">    align-items: center;</span></span><br><span class="line"><span class="string">    margin-bottom: 18px;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .card-title &#123;</span></span><br><span class="line"><span class="string">    display: flex;</span></span><br><span class="line"><span class="string">    align-items: center;</span></span><br><span class="line"><span class="string">    gap: 10px;</span></span><br><span class="line"><span class="string">    font-size: 1.05rem;</span></span><br><span class="line"><span class="string">    font-weight: 600;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .card-icon &#123;</span></span><br><span class="line"><span class="string">    font-size: 1.3rem;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .running-badge &#123;</span></span><br><span class="line"><span class="string">    font-size: 0.7rem;</span></span><br><span class="line"><span class="string">    padding: 3px 10px;</span></span><br><span class="line"><span class="string">    border-radius: 12px;</span></span><br><span class="line"><span class="string">    font-weight: 600;</span></span><br><span class="line"><span class="string">    background: var(--green-glow);</span></span><br><span class="line"><span class="string">    color: var(--green);</span></span><br><span class="line"><span class="string">    display: none;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .running-badge.active &#123; display: inline-block; &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  /* Toggle */</span></span><br><span class="line"><span class="string">  .toggle &#123;</span></span><br><span class="line"><span class="string">    position: relative;</span></span><br><span class="line"><span class="string">    width: 52px; height: 28px;</span></span><br><span class="line"><span class="string">    cursor: pointer;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .toggle input &#123; display: none; &#125;</span></span><br><span class="line"><span class="string">  .toggle .slider &#123;</span></span><br><span class="line"><span class="string">    position: absolute;</span></span><br><span class="line"><span class="string">    inset: 0;</span></span><br><span class="line"><span class="string">    background: var(--surface2);</span></span><br><span class="line"><span class="string">    border: 2px solid var(--border);</span></span><br><span class="line"><span class="string">    border-radius: 14px;</span></span><br><span class="line"><span class="string">    transition: all 0.3s;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .toggle .slider::before &#123;</span></span><br><span class="line"><span class="string">    content: &#x27;&#x27;;</span></span><br><span class="line"><span class="string">    position: absolute;</span></span><br><span class="line"><span class="string">    width: 20px; height: 20px;</span></span><br><span class="line"><span class="string">    left: 2px; top: 2px;</span></span><br><span class="line"><span class="string">    background: var(--text2);</span></span><br><span class="line"><span class="string">    border-radius: 50%;</span></span><br><span class="line"><span class="string">    transition: all 0.3s;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .toggle input:checked + .slider &#123;</span></span><br><span class="line"><span class="string">    background: var(--accent);</span></span><br><span class="line"><span class="string">    border-color: var(--accent);</span></span><br><span class="line"><span class="string">    box-shadow: 0 0 12px var(--accent-glow);</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .toggle input:checked + .slider::before &#123;</span></span><br><span class="line"><span class="string">    transform: translateX(24px);</span></span><br><span class="line"><span class="string">    background: white;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  /* Form controls */</span></span><br><span class="line"><span class="string">  .field-group &#123;</span></span><br><span class="line"><span class="string">    display: grid;</span></span><br><span class="line"><span class="string">    grid-template-columns: 1fr 1fr;</span></span><br><span class="line"><span class="string">    gap: 12px;</span></span><br><span class="line"><span class="string">    margin-top: 14px;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .field &#123;</span></span><br><span class="line"><span class="string">    display: flex;</span></span><br><span class="line"><span class="string">    flex-direction: column;</span></span><br><span class="line"><span class="string">    gap: 6px;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .field.full &#123; grid-column: 1 / -1; &#125;</span></span><br><span class="line"><span class="string">  .field label &#123;</span></span><br><span class="line"><span class="string">    font-size: 0.78rem;</span></span><br><span class="line"><span class="string">    color: var(--text2);</span></span><br><span class="line"><span class="string">    font-weight: 500;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .field input, .field select &#123;</span></span><br><span class="line"><span class="string">    background: var(--surface2);</span></span><br><span class="line"><span class="string">    border: 1px solid var(--border);</span></span><br><span class="line"><span class="string">    border-radius: var(--radius-sm);</span></span><br><span class="line"><span class="string">    padding: 10px 14px;</span></span><br><span class="line"><span class="string">    color: var(--text);</span></span><br><span class="line"><span class="string">    font-size: 0.9rem;</span></span><br><span class="line"><span class="string">    font-family: inherit;</span></span><br><span class="line"><span class="string">    outline: none;</span></span><br><span class="line"><span class="string">    transition: border-color 0.2s;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .field input:focus, .field select:focus &#123;</span></span><br><span class="line"><span class="string">    border-color: var(--accent);</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .field input[type=&quot;number&quot;] &#123;</span></span><br><span class="line"><span class="string">    -moz-appearance: textfield;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  /* Time list */</span></span><br><span class="line"><span class="string">  .time-list &#123;</span></span><br><span class="line"><span class="string">    display: flex;</span></span><br><span class="line"><span class="string">    flex-wrap: wrap;</span></span><br><span class="line"><span class="string">    gap: 8px;</span></span><br><span class="line"><span class="string">    margin-top: 8px;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .time-tag &#123;</span></span><br><span class="line"><span class="string">    display: inline-flex;</span></span><br><span class="line"><span class="string">    align-items: center;</span></span><br><span class="line"><span class="string">    gap: 6px;</span></span><br><span class="line"><span class="string">    padding: 6px 12px;</span></span><br><span class="line"><span class="string">    background: var(--surface2);</span></span><br><span class="line"><span class="string">    border: 1px solid var(--border);</span></span><br><span class="line"><span class="string">    border-radius: 8px;</span></span><br><span class="line"><span class="string">    font-size: 0.85rem;</span></span><br><span class="line"><span class="string">    font-weight: 500;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .time-tag .remove &#123;</span></span><br><span class="line"><span class="string">    cursor: pointer;</span></span><br><span class="line"><span class="string">    color: var(--red);</span></span><br><span class="line"><span class="string">    font-size: 1rem;</span></span><br><span class="line"><span class="string">    line-height: 1;</span></span><br><span class="line"><span class="string">    opacity: 0.7;</span></span><br><span class="line"><span class="string">    transition: opacity 0.2s;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .time-tag .remove:hover &#123; opacity: 1; &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  .add-time-row &#123;</span></span><br><span class="line"><span class="string">    display: flex;</span></span><br><span class="line"><span class="string">    gap: 8px;</span></span><br><span class="line"><span class="string">    margin-top: 10px;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .add-time-row input &#123;</span></span><br><span class="line"><span class="string">    flex: 1;</span></span><br><span class="line"><span class="string">    background: var(--surface2);</span></span><br><span class="line"><span class="string">    border: 1px solid var(--border);</span></span><br><span class="line"><span class="string">    border-radius: var(--radius-sm);</span></span><br><span class="line"><span class="string">    padding: 8px 12px;</span></span><br><span class="line"><span class="string">    color: var(--text);</span></span><br><span class="line"><span class="string">    font-family: inherit;</span></span><br><span class="line"><span class="string">    outline: none;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .btn-add &#123;</span></span><br><span class="line"><span class="string">    background: var(--accent);</span></span><br><span class="line"><span class="string">    border: none;</span></span><br><span class="line"><span class="string">    border-radius: var(--radius-sm);</span></span><br><span class="line"><span class="string">    padding: 8px 16px;</span></span><br><span class="line"><span class="string">    color: white;</span></span><br><span class="line"><span class="string">    font-weight: 600;</span></span><br><span class="line"><span class="string">    cursor: pointer;</span></span><br><span class="line"><span class="string">    font-size: 0.85rem;</span></span><br><span class="line"><span class="string">    transition: opacity 0.2s;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .btn-add:hover &#123; opacity: 0.85; &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  /* Send button */</span></span><br><span class="line"><span class="string">  .btn-send &#123;</span></span><br><span class="line"><span class="string">    width: 100%;</span></span><br><span class="line"><span class="string">    padding: 14px;</span></span><br><span class="line"><span class="string">    background: linear-gradient(135deg, #6c5ce7, #a29bfe);</span></span><br><span class="line"><span class="string">    border: none;</span></span><br><span class="line"><span class="string">    border-radius: var(--radius-sm);</span></span><br><span class="line"><span class="string">    color: white;</span></span><br><span class="line"><span class="string">    font-size: 1rem;</span></span><br><span class="line"><span class="string">    font-weight: 600;</span></span><br><span class="line"><span class="string">    font-family: inherit;</span></span><br><span class="line"><span class="string">    cursor: pointer;</span></span><br><span class="line"><span class="string">    margin-top: 20px;</span></span><br><span class="line"><span class="string">    transition: opacity 0.2s, transform 0.1s;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .btn-send:hover &#123; opacity: 0.9; &#125;</span></span><br><span class="line"><span class="string">  .btn-send:active &#123; transform: scale(0.98); &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  /* Status panel */</span></span><br><span class="line"><span class="string">  .status-grid &#123;</span></span><br><span class="line"><span class="string">    display: grid;</span></span><br><span class="line"><span class="string">    grid-template-columns: 1fr 1fr;</span></span><br><span class="line"><span class="string">    gap: 10px;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .status-item &#123;</span></span><br><span class="line"><span class="string">    background: var(--surface2);</span></span><br><span class="line"><span class="string">    border-radius: var(--radius-sm);</span></span><br><span class="line"><span class="string">    padding: 14px;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .status-item .label &#123;</span></span><br><span class="line"><span class="string">    font-size: 0.75rem;</span></span><br><span class="line"><span class="string">    color: var(--text2);</span></span><br><span class="line"><span class="string">    margin-bottom: 4px;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .status-item .value &#123;</span></span><br><span class="line"><span class="string">    font-size: 0.95rem;</span></span><br><span class="line"><span class="string">    font-weight: 600;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .status-item .value.on &#123; color: var(--green); &#125;</span></span><br><span class="line"><span class="string">  .status-item .value.off &#123; color: var(--text2); &#125;</span></span><br><span class="line"><span class="string">  .status-item .value.warn &#123; color: var(--yellow); &#125;</span></span><br><span class="line"><span class="string">  .status-item.full &#123; grid-column: 1 / -1; &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  /* Toast */</span></span><br><span class="line"><span class="string">  .toast &#123;</span></span><br><span class="line"><span class="string">    position: fixed;</span></span><br><span class="line"><span class="string">    bottom: 30px;</span></span><br><span class="line"><span class="string">    left: 50%;</span></span><br><span class="line"><span class="string">    transform: translateX(-50%) translateY(100px);</span></span><br><span class="line"><span class="string">    background: var(--surface);</span></span><br><span class="line"><span class="string">    border: 1px solid var(--accent);</span></span><br><span class="line"><span class="string">    color: var(--text);</span></span><br><span class="line"><span class="string">    padding: 12px 24px;</span></span><br><span class="line"><span class="string">    border-radius: var(--radius-sm);</span></span><br><span class="line"><span class="string">    font-size: 0.85rem;</span></span><br><span class="line"><span class="string">    font-weight: 500;</span></span><br><span class="line"><span class="string">    box-shadow: 0 8px 32px rgba(0,0,0,0.4);</span></span><br><span class="line"><span class="string">    opacity: 0;</span></span><br><span class="line"><span class="string">    transition: all 0.3s;</span></span><br><span class="line"><span class="string">    z-index: 100;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  .toast.show &#123;</span></span><br><span class="line"><span class="string">    opacity: 1;</span></span><br><span class="line"><span class="string">    transform: translateX(-50%) translateY(0);</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  /* Responsive */</span></span><br><span class="line"><span class="string">  @media (max-width: 500px) &#123;</span></span><br><span class="line"><span class="string">    body &#123; padding: 12px; &#125;</span></span><br><span class="line"><span class="string">    .header h1 &#123; font-size: 1.5rem; &#125;</span></span><br><span class="line"><span class="string">    .field-group &#123; grid-template-columns: 1fr; &#125;</span></span><br><span class="line"><span class="string">    .status-grid &#123; grid-template-columns: 1fr; &#125;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">&lt;/style&gt;</span></span><br><span class="line"><span class="string">&lt;/head&gt;</span></span><br><span class="line"><span class="string">&lt;body&gt;</span></span><br><span class="line"><span class="string">&lt;div class=&quot;container&quot;&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  &lt;!-- Header --&gt;</span></span><br><span class="line"><span class="string">  &lt;div class=&quot;header&quot;&gt;</span></span><br><span class="line"><span class="string">    &lt;h1&gt;🐢 &lt;span&gt;Autofeed 控制面板&lt;/span&gt;&lt;/h1&gt;</span></span><br><span class="line"><span class="string">    &lt;div class=&quot;status-badge disconnected&quot; id=&quot;mqttBadge&quot;&gt;</span></span><br><span class="line"><span class="string">      &lt;span class=&quot;status-dot&quot;&gt;&lt;/span&gt;</span></span><br><span class="line"><span class="string">      &lt;span id=&quot;mqttStatusText&quot;&gt;未连接&lt;/span&gt;</span></span><br><span class="line"><span class="string">    &lt;/div&gt;</span></span><br><span class="line"><span class="string">  &lt;/div&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  &lt;!-- 电机控制 --&gt;</span></span><br><span class="line"><span class="string">  &lt;div class=&quot;card&quot; id=&quot;motorCard&quot;&gt;</span></span><br><span class="line"><span class="string">    &lt;div class=&quot;card-header&quot;&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;card-title&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;span class=&quot;card-icon&quot;&gt;⚙️&lt;/span&gt; 电机控制</span></span><br><span class="line"><span class="string">        &lt;span class=&quot;running-badge&quot; id=&quot;motorRunBadge&quot;&gt;运行中&lt;/span&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;label class=&quot;toggle&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;input type=&quot;checkbox&quot; id=&quot;motorEnabled&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;span class=&quot;slider&quot;&gt;&lt;/span&gt;</span></span><br><span class="line"><span class="string">      &lt;/label&gt;</span></span><br><span class="line"><span class="string">    &lt;/div&gt;</span></span><br><span class="line"><span class="string">    &lt;div class=&quot;field-group&quot;&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;field&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;label&gt;每次运行时长 (秒)&lt;/label&gt;</span></span><br><span class="line"><span class="string">        &lt;input type=&quot;number&quot; id=&quot;motorRunSeconds&quot; value=&quot;30&quot; min=&quot;1&quot; max=&quot;3600&quot;&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;field&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;label&gt;间隔天数&lt;/label&gt;</span></span><br><span class="line"><span class="string">        &lt;select id=&quot;motorIntervalDays&quot;&gt;</span></span><br><span class="line"><span class="string">          &lt;option value=&quot;1&quot;&gt;每天&lt;/option&gt;</span></span><br><span class="line"><span class="string">          &lt;option value=&quot;2&quot;&gt;隔天 (每2天)&lt;/option&gt;</span></span><br><span class="line"><span class="string">          &lt;option value=&quot;3&quot;&gt;每3天&lt;/option&gt;</span></span><br><span class="line"><span class="string">          &lt;option value=&quot;4&quot;&gt;每4天&lt;/option&gt;</span></span><br><span class="line"><span class="string">          &lt;option value=&quot;5&quot;&gt;每5天&lt;/option&gt;</span></span><br><span class="line"><span class="string">        &lt;/select&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;field full&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;label&gt;定时启动时间&lt;/label&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;time-list&quot; id=&quot;timeList&quot;&gt;&lt;/div&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;add-time-row&quot;&gt;</span></span><br><span class="line"><span class="string">          &lt;input type=&quot;time&quot; id=&quot;newTime&quot; value=&quot;08:00&quot;&gt;</span></span><br><span class="line"><span class="string">          &lt;button class=&quot;btn-add&quot; onclick=&quot;addTime()&quot;&gt;添加&lt;/button&gt;</span></span><br><span class="line"><span class="string">        &lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">    &lt;/div&gt;</span></span><br><span class="line"><span class="string">  &lt;/div&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  &lt;!-- 进水泵控制 --&gt;</span></span><br><span class="line"><span class="string">  &lt;div class=&quot;card&quot;&gt;</span></span><br><span class="line"><span class="string">    &lt;div class=&quot;card-header&quot;&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;card-title&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;span class=&quot;card-icon&quot;&gt;💧&lt;/span&gt; 进水泵</span></span><br><span class="line"><span class="string">        &lt;span class=&quot;running-badge&quot; id=&quot;pumpInRunBadge&quot;&gt;运行中&lt;/span&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;label class=&quot;toggle&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;input type=&quot;checkbox&quot; id=&quot;pumpInEnabled&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;span class=&quot;slider&quot;&gt;&lt;/span&gt;</span></span><br><span class="line"><span class="string">      &lt;/label&gt;</span></span><br><span class="line"><span class="string">    &lt;/div&gt;</span></span><br><span class="line"><span class="string">    &lt;p style=&quot;color:var(--text2);font-size:0.85rem;&quot;&gt;开启后由水位传感器自动控制进水泵工作&lt;/p&gt;</span></span><br><span class="line"><span class="string">  &lt;/div&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  &lt;!-- 循环泵控制 --&gt;</span></span><br><span class="line"><span class="string">  &lt;div class=&quot;card&quot;&gt;</span></span><br><span class="line"><span class="string">    &lt;div class=&quot;card-header&quot;&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;card-title&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;span class=&quot;card-icon&quot;&gt;🔄&lt;/span&gt; 循环泵</span></span><br><span class="line"><span class="string">        &lt;span class=&quot;running-badge&quot; id=&quot;pumpOutRunBadge&quot;&gt;运行中&lt;/span&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;label class=&quot;toggle&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;input type=&quot;checkbox&quot; id=&quot;pumpOutEnabled&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;span class=&quot;slider&quot;&gt;&lt;/span&gt;</span></span><br><span class="line"><span class="string">      &lt;/label&gt;</span></span><br><span class="line"><span class="string">    &lt;/div&gt;</span></span><br><span class="line"><span class="string">    &lt;div class=&quot;field-group&quot;&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;field&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;label&gt;开启时间 (秒)&lt;/label&gt;</span></span><br><span class="line"><span class="string">        &lt;input type=&quot;number&quot; id=&quot;pumpOutOn&quot; value=&quot;30&quot; min=&quot;1&quot; max=&quot;3600&quot;&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;field&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;label&gt;关闭时间 (秒)&lt;/label&gt;</span></span><br><span class="line"><span class="string">        &lt;input type=&quot;number&quot; id=&quot;pumpOutOff&quot; value=&quot;30&quot; min=&quot;1&quot; max=&quot;3600&quot;&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">    &lt;/div&gt;</span></span><br><span class="line"><span class="string">  &lt;/div&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  &lt;!-- 发送按钮 --&gt;</span></span><br><span class="line"><span class="string">  &lt;button class=&quot;btn-send&quot; onclick=&quot;sendCommand()&quot;&gt;📡 发送配置&lt;/button&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  &lt;!-- 设备状态 --&gt;</span></span><br><span class="line"><span class="string">  &lt;div class=&quot;card&quot; style=&quot;margin-top: 16px;&quot;&gt;</span></span><br><span class="line"><span class="string">    &lt;div class=&quot;card-header&quot;&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;card-title&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;span class=&quot;card-icon&quot;&gt;📊&lt;/span&gt; 设备状态</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">    &lt;/div&gt;</span></span><br><span class="line"><span class="string">    &lt;div class=&quot;status-grid&quot;&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;status-item&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;label&quot;&gt;电机&lt;/div&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;value off&quot; id=&quot;stMotor&quot;&gt;--&lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;status-item&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;label&quot;&gt;进水泵&lt;/div&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;value off&quot; id=&quot;stPumpIn&quot;&gt;--&lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;status-item&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;label&quot;&gt;循环泵&lt;/div&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;value off&quot; id=&quot;stPumpOut&quot;&gt;--&lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;status-item&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;label&quot;&gt;WiFi&lt;/div&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;value off&quot; id=&quot;stWifi&quot;&gt;--&lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;status-item&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;label&quot;&gt;低水位报警&lt;/div&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;value off&quot; id=&quot;stWaterLow&quot;&gt;--&lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;status-item&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;label&quot;&gt;水位已满&lt;/div&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;value off&quot; id=&quot;stWaterHigh&quot;&gt;--&lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;div class=&quot;status-item full&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;label&quot;&gt;设备时间&lt;/div&gt;</span></span><br><span class="line"><span class="string">        &lt;div class=&quot;value&quot; id=&quot;stTime&quot;&gt;--&lt;/div&gt;</span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">    &lt;/div&gt;</span></span><br><span class="line"><span class="string">  &lt;/div&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;/div&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;!-- Toast --&gt;</span></span><br><span class="line"><span class="string">&lt;div class=&quot;toast&quot; id=&quot;toast&quot;&gt;&lt;/div&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;script&gt;</span></span><br><span class="line"><span class="string">// ==================== HiveMQ 配置 (请填写) ====================</span></span><br><span class="line"><span class="string">const MQTT_CONFIG = &#123;</span></span><br><span class="line"><span class="string">  broker: &#x27;wss://xxx.hivemq.cloud:8884/mqtt&#x27;,   // HiveMQ WebSocket 地址</span></span><br><span class="line"><span class="string">  username: &#x27;name&#x27;,                               // HiveMQ 项目中新建用户，用户名</span></span><br><span class="line"><span class="string">  password: &#x27;password&#x27;,                               // HiveMQ 密码</span></span><br><span class="line"><span class="string">  clientId: &#x27;autofeed-web-&#x27; + Math.random().toString(16).slice(2, 8),</span></span><br><span class="line"><span class="string">&#125;;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">const TOPIC_CMD = &#x27;autofeed/cmd&#x27;;</span></span><br><span class="line"><span class="string">const TOPIC_STATUS = &#x27;autofeed/status&#x27;;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">// ==================== 全局状态 ====================</span></span><br><span class="line"><span class="string">let client = null;</span></span><br><span class="line"><span class="string">let scheduleTimes = [];</span></span><br><span class="line"><span class="string">let lastInteraction = 0; // 上次用户操作时间</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">// ==================== MQTT 连接 ====================</span></span><br><span class="line"><span class="string">function connectMQTT() &#123;</span></span><br><span class="line"><span class="string">  const badge = document.getElementById(&#x27;mqttBadge&#x27;);</span></span><br><span class="line"><span class="string">  const statusText = document.getElementById(&#x27;mqttStatusText&#x27;);</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  statusText.textContent = &#x27;连接中...&#x27;;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  client = mqtt.connect(MQTT_CONFIG.broker, &#123;</span></span><br><span class="line"><span class="string">    username: MQTT_CONFIG.username,</span></span><br><span class="line"><span class="string">    password: MQTT_CONFIG.password,</span></span><br><span class="line"><span class="string">    clientId: MQTT_CONFIG.clientId,</span></span><br><span class="line"><span class="string">    protocolVersion: 4,</span></span><br><span class="line"><span class="string">    clean: true,</span></span><br><span class="line"><span class="string">    reconnectPeriod: 5000,</span></span><br><span class="line"><span class="string">  &#125;);</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  client.on(&#x27;connect&#x27;, () =&gt; &#123;</span></span><br><span class="line"><span class="string">    badge.className = &#x27;status-badge connected&#x27;;</span></span><br><span class="line"><span class="string">    statusText.textContent = &#x27;已连接&#x27;;</span></span><br><span class="line"><span class="string">    client.subscribe(TOPIC_STATUS);</span></span><br><span class="line"><span class="string">    showToast(&#x27;✅ MQTT 已连接&#x27;);</span></span><br><span class="line"><span class="string">  &#125;);</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  client.on(&#x27;error&#x27;, (err) =&gt; &#123;</span></span><br><span class="line"><span class="string">    badge.className = &#x27;status-badge disconnected&#x27;;</span></span><br><span class="line"><span class="string">    statusText.textContent = &#x27;连接失败&#x27;;</span></span><br><span class="line"><span class="string">    console.error(&#x27;MQTT error:&#x27;, err);</span></span><br><span class="line"><span class="string">  &#125;);</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  client.on(&#x27;offline&#x27;, () =&gt; &#123;</span></span><br><span class="line"><span class="string">    badge.className = &#x27;status-badge disconnected&#x27;;</span></span><br><span class="line"><span class="string">    statusText.textContent = &#x27;已断开&#x27;;</span></span><br><span class="line"><span class="string">  &#125;);</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  client.on(&#x27;reconnect&#x27;, () =&gt; &#123;</span></span><br><span class="line"><span class="string">    statusText.textContent = &#x27;重连中...&#x27;;</span></span><br><span class="line"><span class="string">  &#125;);</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  client.on(&#x27;message&#x27;, (topic, message) =&gt; &#123;</span></span><br><span class="line"><span class="string">    if (topic === TOPIC_STATUS) &#123;</span></span><br><span class="line"><span class="string">      try &#123;</span></span><br><span class="line"><span class="string">        const data = JSON.parse(message.toString());</span></span><br><span class="line"><span class="string">        updateStatusPanel(data);</span></span><br><span class="line"><span class="string">      &#125; catch (e) &#123;</span></span><br><span class="line"><span class="string">        console.error(&#x27;Parse error:&#x27;, e);</span></span><br><span class="line"><span class="string">      &#125;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">  &#125;);</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">// ==================== 更新状态面板 ====================</span></span><br><span class="line"><span class="string">function updateStatusPanel(data) &#123;</span></span><br><span class="line"><span class="string">  setStatus(&#x27;stMotor&#x27;, data.motor?.running, data.motor?.enabled ? &#x27;已启用&#x27; : &#x27;已禁用&#x27;);</span></span><br><span class="line"><span class="string">  setStatus(&#x27;stPumpIn&#x27;, data.pumpIn?.running, data.pumpIn?.enabled ? &#x27;已启用&#x27; : &#x27;已禁用&#x27;);</span></span><br><span class="line"><span class="string">  setStatus(&#x27;stPumpOut&#x27;, data.pumpOut?.running, data.pumpOut?.enabled ? &#x27;已启用&#x27; : &#x27;已禁用&#x27;);</span></span><br><span class="line"><span class="string">  setStatus(&#x27;stWifi&#x27;, data.wifi, &#x27;&#x27;);</span></span><br><span class="line"><span class="string">  setStatusWarn(&#x27;stWaterLow&#x27;, data.waterLow);</span></span><br><span class="line"><span class="string">  setStatusWarn(&#x27;stWaterHigh&#x27;, data.waterHigh);</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  document.getElementById(&#x27;stTime&#x27;).textContent = data.time || &#x27;--&#x27;;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  // 更新运行徽章</span></span><br><span class="line"><span class="string">  toggleBadge(&#x27;motorRunBadge&#x27;, data.motor?.running);</span></span><br><span class="line"><span class="string">  toggleBadge(&#x27;pumpInRunBadge&#x27;, data.pumpIn?.running);</span></span><br><span class="line"><span class="string">  toggleBadge(&#x27;pumpOutRunBadge&#x27;, data.pumpOut?.running);</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  // 同步控制面板与设备状态 (如果用户最近没有操作)</span></span><br><span class="line"><span class="string">  if (Date.now() - lastInteraction &gt; 30000) &#123;</span></span><br><span class="line"><span class="string">    syncUI(data);</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">function setStatus(id, running, fallback) &#123;</span></span><br><span class="line"><span class="string">  const el = document.getElementById(id);</span></span><br><span class="line"><span class="string">  if (running) &#123;</span></span><br><span class="line"><span class="string">    el.textContent = &#x27;运行中&#x27;;</span></span><br><span class="line"><span class="string">    el.className = &#x27;value on&#x27;;</span></span><br><span class="line"><span class="string">  &#125; else &#123;</span></span><br><span class="line"><span class="string">    el.textContent = fallback || &#x27;关闭&#x27;;</span></span><br><span class="line"><span class="string">    el.className = &#x27;value off&#x27;;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">function setStatusWarn(id, active) &#123;</span></span><br><span class="line"><span class="string">  const el = document.getElementById(id);</span></span><br><span class="line"><span class="string">  if (active) &#123;</span></span><br><span class="line"><span class="string">    el.textContent = &#x27;是&#x27;;</span></span><br><span class="line"><span class="string">    el.className = &#x27;value warn&#x27;;</span></span><br><span class="line"><span class="string">  &#125; else &#123;</span></span><br><span class="line"><span class="string">    el.textContent = &#x27;否&#x27;;</span></span><br><span class="line"><span class="string">    el.className = &#x27;value off&#x27;;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">function toggleBadge(id, active) &#123;</span></span><br><span class="line"><span class="string">  const el = document.getElementById(id);</span></span><br><span class="line"><span class="string">  el.className = active ? &#x27;running-badge active&#x27; : &#x27;running-badge&#x27;;</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">// 将设备状态同步到 UI 控件</span></span><br><span class="line"><span class="string">function syncUI(data) &#123;</span></span><br><span class="line"><span class="string">  if (data.motor) &#123;</span></span><br><span class="line"><span class="string">    document.getElementById(&#x27;motorEnabled&#x27;).checked = data.motor.enabled;</span></span><br><span class="line"><span class="string">    document.getElementById(&#x27;motorRunSeconds&#x27;).value = data.motor.runSeconds || 30;</span></span><br><span class="line"><span class="string">    if (data.motor.schedule) &#123;</span></span><br><span class="line"><span class="string">      document.getElementById(&#x27;motorIntervalDays&#x27;).value = data.motor.schedule.intervalDays || 1;</span></span><br><span class="line"><span class="string">      if (data.motor.schedule.times) &#123;</span></span><br><span class="line"><span class="string">        scheduleTimes = [...data.motor.schedule.times];</span></span><br><span class="line"><span class="string">        renderTimes();</span></span><br><span class="line"><span class="string">      &#125;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  if (data.pumpIn) &#123;</span></span><br><span class="line"><span class="string">    document.getElementById(&#x27;pumpInEnabled&#x27;).checked = data.pumpIn.enabled;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  if (data.pumpOut) &#123;</span></span><br><span class="line"><span class="string">    document.getElementById(&#x27;pumpOutEnabled&#x27;).checked = data.pumpOut.enabled;</span></span><br><span class="line"><span class="string">    document.getElementById(&#x27;pumpOutOn&#x27;).value = data.pumpOut.onSeconds || 30;</span></span><br><span class="line"><span class="string">    document.getElementById(&#x27;pumpOutOff&#x27;).value = data.pumpOut.offSeconds || 30;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">// ==================== 时间管理 ====================</span></span><br><span class="line"><span class="string">function addTime() &#123;</span></span><br><span class="line"><span class="string">  const input = document.getElementById(&#x27;newTime&#x27;);</span></span><br><span class="line"><span class="string">  const time = input.value;</span></span><br><span class="line"><span class="string">  if (!time) return;</span></span><br><span class="line"><span class="string">  const formatted = time.substring(0, 5);  // &quot;HH:MM&quot;</span></span><br><span class="line"><span class="string">  if (scheduleTimes.includes(formatted)) &#123;</span></span><br><span class="line"><span class="string">    showToast(&#x27;⚠️ 该时间已存在&#x27;);</span></span><br><span class="line"><span class="string">    return;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">  scheduleTimes.push(formatted);</span></span><br><span class="line"><span class="string">  scheduleTimes.sort();</span></span><br><span class="line"><span class="string">  renderTimes();</span></span><br><span class="line"><span class="string">  recordInteraction();</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">function removeTime(index) &#123;</span></span><br><span class="line"><span class="string">  scheduleTimes.splice(index, 1);</span></span><br><span class="line"><span class="string">  renderTimes();</span></span><br><span class="line"><span class="string">  recordInteraction();</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">function renderTimes() &#123;</span></span><br><span class="line"><span class="string">  const container = document.getElementById(&#x27;timeList&#x27;);</span></span><br><span class="line"><span class="string">  container.innerHTML = scheduleTimes.map((t, i) =&gt;</span></span><br><span class="line"><span class="string">    &#x27;&lt;span class=&quot;time-tag&quot;&gt;&#x27; + t +</span></span><br><span class="line"><span class="string">    &#x27; &lt;span class=&quot;remove&quot; onclick=&quot;removeTime(&#x27; + i + &#x27;)&quot;&gt;×&lt;/span&gt;&lt;/span&gt;&#x27;</span></span><br><span class="line"><span class="string">  ).join(&#x27;&#x27;);</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">// ==================== 发送命令 ====================</span></span><br><span class="line"><span class="string">function sendCommand() &#123;</span></span><br><span class="line"><span class="string">  if (!client || !client.connected) &#123;</span></span><br><span class="line"><span class="string">    showToast(&#x27;❌ MQTT 未连接&#x27;);</span></span><br><span class="line"><span class="string">    return;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  const cmd = &#123;</span></span><br><span class="line"><span class="string">    motor: &#123;</span></span><br><span class="line"><span class="string">      enabled: document.getElementById(&#x27;motorEnabled&#x27;).checked,</span></span><br><span class="line"><span class="string">      runSeconds: parseInt(document.getElementById(&#x27;motorRunSeconds&#x27;).value) || 30,</span></span><br><span class="line"><span class="string">      schedule: &#123;</span></span><br><span class="line"><span class="string">        intervalDays: parseInt(document.getElementById(&#x27;motorIntervalDays&#x27;).value) || 1,</span></span><br><span class="line"><span class="string">        times: [...scheduleTimes],</span></span><br><span class="line"><span class="string">      &#125;,</span></span><br><span class="line"><span class="string">    &#125;,</span></span><br><span class="line"><span class="string">    pumpIn: &#123;</span></span><br><span class="line"><span class="string">      enabled: document.getElementById(&#x27;pumpInEnabled&#x27;).checked,</span></span><br><span class="line"><span class="string">    &#125;,</span></span><br><span class="line"><span class="string">    pumpOut: &#123;</span></span><br><span class="line"><span class="string">      enabled: document.getElementById(&#x27;pumpOutEnabled&#x27;).checked,</span></span><br><span class="line"><span class="string">      onSeconds: parseInt(document.getElementById(&#x27;pumpOutOn&#x27;).value) || 30,</span></span><br><span class="line"><span class="string">      offSeconds: parseInt(document.getElementById(&#x27;pumpOutOff&#x27;).value) || 30,</span></span><br><span class="line"><span class="string">    &#125;,</span></span><br><span class="line"><span class="string">  &#125;;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  client.publish(TOPIC_CMD, JSON.stringify(cmd));</span></span><br><span class="line"><span class="string">  showToast(&#x27;✅ 配置已发送&#x27;);</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">// ==================== Toast ====================</span></span><br><span class="line"><span class="string">function showToast(msg) &#123;</span></span><br><span class="line"><span class="string">  const toast = document.getElementById(&#x27;toast&#x27;);</span></span><br><span class="line"><span class="string">  toast.textContent = msg;</span></span><br><span class="line"><span class="string">  toast.classList.add(&#x27;show&#x27;);</span></span><br><span class="line"><span class="string">  setTimeout(() =&gt; toast.classList.remove(&#x27;show&#x27;), 2500);</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">// ==================== 交互检测 ====================</span></span><br><span class="line"><span class="string">function recordInteraction() &#123;</span></span><br><span class="line"><span class="string">  lastInteraction = Date.now();</span></span><br><span class="line"><span class="string">  console.log(&#x27;User interaction detected, pausing sync for 30s&#x27;);</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">function initInteractionListeners() &#123;</span></span><br><span class="line"><span class="string">  document.querySelectorAll(&#x27;input, select&#x27;).forEach(el =&gt; &#123;</span></span><br><span class="line"><span class="string">    el.addEventListener(&#x27;input&#x27;, recordInteraction);</span></span><br><span class="line"><span class="string">    el.addEventListener(&#x27;change&#x27;, recordInteraction);</span></span><br><span class="line"><span class="string">  &#125;);</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">// ==================== 初始化 ====================</span></span><br><span class="line"><span class="string">renderTimes();</span></span><br><span class="line"><span class="string">initInteractionListeners();</span></span><br><span class="line"><span class="string">connectMQTT();</span></span><br><span class="line"><span class="string">&lt;/script&gt;</span></span><br><span class="line"><span class="string">&lt;/body&gt;</span></span><br><span class="line"><span class="string">&lt;/html&gt;`</span>;</span><br></pre></td></tr></table></figure><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Autofeed/worker_V2.2.webp" alt="worker_V2"></p><p>目前页面使用的是 Emoji 图标，可以根据喜好自行更换：<br>比如主标题图标 (第 352 行左右)：<br><code>&lt;h1&gt;🐢 &lt;span&gt;Autofeed 2.2&lt;/span&gt;&lt;/h1&gt;</code><br>可以将 🐢 换成其他 Emoji。</p><p>功能卡片图标：<br>电机控制: ⚙️ (第 363 行左右)<br>进水泵: 💧 (第 401 行左右)<br>循环泵: 🔄 (第 416 行左右)<br>设备状态: 📊 (第 443 行左右)</p><p>由于 Cloudflare Worker 是单文件部署，最简单的方法是使用 Emoji Favicon。所以我在 <code>&lt;head&gt;</code> 标签内添加一行代码作为网站的 Favicon：<br><code>&lt;link rel=&quot;icon&quot; href=&quot;https://fav.farm/🐢&quot; /&gt;</code><br>注：fav.farm 是一个直接把 Emoji 变成图标的开源服务，也可以使用 Base64 图片代码</p><h4 id="手动部署"><a href="#手动部署" class="headerlink" title="手动部署"></a>手动部署</h4><ol><li><p>访问 dash.cloudflare.com 并登录。</p></li><li><p>创建 Worker。</p><ul><li>在左侧菜单栏选择 Workers &amp; Pages (Workers 和页面)。</li><li>点击 Create application (创建应用程序)。</li><li>点击 Create Worker (创建 Worker)。</li><li>给你的 Worker 起个名字（比如 autofeed），然后点击右下角的 Deploy (部署)。</li></ul></li><li><p>部署完成后，点击编辑代码按钮。</p></li><li><p>删掉代码编辑器里所有默认的代码。将本地的 worker.js 代码全部复制粘贴进 Cloudflare 的编辑器里。</p></li><li><p>点击右上角的 Save and deploy (保存并部署)。</p></li><li><p>部署成功后，会有一个 .workers.dev 结尾的网址。点击网址就能看到 Autofeed 2.2 的控制面板了。<br>当然，如果有域名的话可以绑定自己的域名。</p></li></ol><h4 id="使用前配置"><a href="#使用前配置" class="headerlink" title="使用前配置"></a>使用前配置</h4><p>我使用的是 HiveMQ，国内的有 EMQX，设置都一样。<br>注册账号后新建一个 <code>Cloud Clusters</code>，可以获得 URL 了。再添加一个 <code>credentials</code>，设置用户名和密码，权限选择 <code>publish and subscribe</code>。<br>免费版可以支持 100 个设备，流量 10G&#x2F;month。发布者，订阅者都算设备，所以 worker 也算一台。</p><p>EMQX 免费版支持 1000 台设备，连接时间 100 万分钟&#x2F;月，流量 1G&#x2F;month。</p><blockquote><p><strong>Arduino</strong> — 修改 <code>Autofeed2.2.ino</code> 顶部的 HiveMQ 凭据:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> MQTT_BROKER   <span class="string">&quot;your-cluster.hivemq.cloud&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MQTT_USER     <span class="string">&quot;your-username&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MQTT_PASS     <span class="string">&quot;your-password&quot;</span></span></span><br></pre></td></tr></table></figure><p><strong>Worker</strong> — 修改 <code>worker.js</code> 中的 <code>MQTT_CONFIG</code>:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">broker</span>: <span class="string">&#x27;wss://your-cluster.hivemq.cloud:8884/mqtt&#x27;</span>,</span><br><span class="line"><span class="attr">username</span>: <span class="string">&#x27;your-username&#x27;</span>,</span><br><span class="line"><span class="attr">password</span>: <span class="string">&#x27;your-password&#x27;</span>,</span><br></pre></td></tr></table></figure></blockquote><h3 id="功能验证"><a href="#功能验证" class="headerlink" title="功能验证"></a>功能验证</h3><ol><li><strong>WiFi 配网</strong>: 上电后，通过 ESPTouch App 配网，串口监视器查看连接状态。<strong>注意，Wi-Fi 只支持 2.4G，APP 里选择 EspTouch V2，加密模式 1</strong></li><li><strong>MQTT 连接</strong>: 填写 HiveMQ 凭据后，串口监视器确认 MQTT 连接成功</li><li><strong>Cloudflare Worker</strong>: 访问 URL 查看控制面板</li><li><strong>远程控制</strong>: 在面板上操作开关&#x2F;设置参数，观察 ESP32 串口输出及硬件响应<br><strong>注意：web 面板新修改的设置需在 30 秒内点击发送按钮，不然会刷新到当前的配置参数。</strong></li><li><strong>水位检测</strong>: 模拟 GPIO16&#x2F;17 信号变化，观察进水泵响应</li><li><strong>定时调度</strong>: 设置定时任务后观察电机是否按时启动</li></ol><p>ESPTouch App 下载：<a href="https://github.com/EspressifApp/EsptouchForAndroid/releases/download/v2.4.0/esptouch-v2.4.0.apk">https://github.com/EspressifApp/EsptouchForAndroid/releases/download/v2.4.0/esptouch-v2.4.0.apk</a></p><p>不知道为什么乐鑫不把 APP 放个应用市场，Google Play 里倒是有其他人开发的配网应用，不过我还没试过。</p><p>以上所有源文件在此，仅供参考，有问题自己搜索或问 AI：<br><a href="https://github.com/harry10086/Autofeed">https://github.com/harry10086/Autofeed</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简介&lt;/h3&gt;&lt;p&gt;去年我升级了乌龟的自动喂食器：&lt;a href=&quot;https://mianao.info/diy-turtle-auto-feeder-v2-</summary>
      
    
    
    
    <category term="DIY" scheme="https://mianao.info/categories/DIY/"/>
    
    
    <category term="Arduino" scheme="https://mianao.info/tags/Arduino/"/>
    
  </entry>
  
  <entry>
    <title>PC电源改装直流稳压源升级加上语音控制</title>
    <link href="https://mianao.info/pc-power-supply-adjustable-dc-voice-control/"/>
    <id>https://mianao.info/pc-power-supply-adjustable-dc-voice-control/</id>
    <published>2026-02-13T16:00:00.000Z</published>
    <updated>2026-02-13T16:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>上次用 SK150 模块改装了一台电源：<a href="https://mianao.info/repurpose-pc-power-supply-dc-regulator/">变废为宝-将淘汰的PC电源改装成直流稳压源</a>，一直想升级一下。为什么呢，一个原因是之前打印的是白色，和灰色的模块不搭，另一个原因是想增加 USB 接口，直接输出 5V，还有一个是电源按钮忘记把指示灯接上了。然后就是在使用时，发现要是加个语音识别功能就最好了，两只手在测量时，嘴里叫一声打开电源，关闭电源，就非常方便了。趁着年前有点时间，功能全都加上。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/PC-SK150/power1.jpg" alt="power1"></p><h3 id="语音识别"><a href="#语音识别" class="headerlink" title="语音识别"></a>语音识别</h3><p>语音模块淘宝一搜就有很多，我买的是 ASR-PRO 核心板 4M 版本，花了 9.5。<br>模块问题不大，但配套的开发软件就是垃圾，说明文档也很烂，搞的我重新写了几次，就因为开发软件逻辑混乱，自动保存不知道保存的啥，另存为后自动保存又自己改名字，什么配置模式，编程模式，字符编程随便点一下，之前的工作就废了。反正就是乱七八糟的，就这样的玩意儿居然还拿出来商用，太业余了。<br>还有一个就是固件下载之前一定要先生成模型，也就是安装固件里的文字生成语音模型。这个必须用手机号实名注册才行。</p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/PC-SK150/asr.png" alt="asr"></p><p>按照模块的开发板设计的：</p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/PC-SK150/pcb.png" alt="pcb"></p><p>源文件使用嘉立创专业版，没用 KiCAD 是因为板子很简单又小，方便免费打板：</p><p>ADJpower_ASR_V1.0 链接: <a href="https://pan.baidu.com/s/18X4nqUIlhQyUX2NETzVrwA?pwd=s7fu">https://pan.baidu.com/s/18X4nqUIlhQyUX2NETzVrwA?pwd=s7fu</a> 提取码: s7fu</p><p>SK150 模块有标准的 Modbus-RTU (CRC-16&#x2F;MODBUS) 协议，见SK150 模块产品说明书：<br>链接：<a href="https://pan.quark.cn/s/a023f8d13edf">https://pan.quark.cn/s/a023f8d13edf</a>  提取码：AX8x</p><p>但是，这个说明书里有问题，<strong>电压值是 x100 后转换成十六进制，电流值是 x1000 再转换成十六进制。</strong> 不然按照说明书来电流调整都会小 10倍。</p><p>语音模块的代码如下，我只加了开关输出，一些我常用的电压电流值：<br>输出开启 (ON)：<code>01 06 00 12 00 01 E8 0F</code><br>输出关闭 (OFF)：<code>01 06 00 12 00 00 29 CF</code></p><table><thead><tr><th>电压 (V)</th><th>值（单位：0.01V）</th><th>十六进制指令</th><th>语音 ID</th></tr></thead><tbody><tr><td>3.6</td><td>360</td><td>01 06 00 00 01 68 89 B4</td><td>2</td></tr><tr><td>4.2</td><td>420</td><td>01 06 00 00 01 A4 89 E1</td><td>3</td></tr><tr><td>8.4</td><td>840</td><td>01 06 00 00 03 48 89 0C</td><td>4</td></tr><tr><td>12.0</td><td>1200</td><td>01 06 00 00 04 B0 8A BE</td><td>5</td></tr><tr><td>12.6</td><td>1260</td><td>01 06 00 00 04 EC 8A 87</td><td>6</td></tr><tr><td>16.8</td><td>1680</td><td>01 06 00 00 06 90 8A 06</td><td>7</td></tr><tr><td>20.0</td><td>2000</td><td>01 06 00 00 07 D0 8A 66</td><td>8</td></tr><tr><td>24.0</td><td>2400</td><td>01 06 00 00 09 60 8F B2</td><td>9</td></tr><tr><td>25.2</td><td>2520</td><td>01 06 00 00 09 D8 8F C0</td><td>10</td></tr><tr><td>29.4</td><td>2940</td><td>01 06 00 00 0B 7C 8F 1B</td><td>11</td></tr><tr><td>30.0</td><td>3000</td><td>01 06 00 00 0B B8 8E 88</td><td>12</td></tr><tr><td>36.0</td><td>3600</td><td>01 06 00 00 0E 10 8C 66</td><td>13</td></tr></tbody></table><table><thead><tr><th>电流 (A)</th><th>值 (单位： 0.001A)</th><th>十六进制指令</th><th>语音ID</th></tr></thead><tbody><tr><td>0.2</td><td>200</td><td>01 06 00 01 00 C8 D9 9C</td><td>14</td></tr><tr><td>0.5</td><td>500</td><td>01 06 00 01 01 F4 D8 1D</td><td>15</td></tr><tr><td>1.0</td><td>1000</td><td>01 06 00 01 03 E8 D8 B4</td><td>16</td></tr><tr><td>1.5</td><td>1500</td><td>01 06 00 01 05 DC DA C3</td><td>17</td></tr><tr><td>2.0</td><td>2000</td><td>01 06 00 01 07 D0 DB A6</td><td>18</td></tr><tr><td>2.5</td><td>2500</td><td>01 06 00 01 09 C4 DF C9</td><td>19</td></tr><tr><td>3.0</td><td>3000</td><td>01 06 00 01 0B B8 DF 48</td><td>20</td></tr><tr><td>3.5</td><td>3500</td><td>01 06 00 01 0D AC DC E7</td><td>21</td></tr><tr><td>4.0</td><td>4000</td><td>01 06 00 01 0F A0 DD 82</td><td>22</td></tr><tr><td>4.5</td><td>4500</td><td>01 06 00 01 11 94 D5 F5</td><td>23</td></tr><tr><td>5.0</td><td>5000</td><td>01 06 00 01 13 88 D5 5C</td><td>24</td></tr><tr><td>6.0</td><td>6000</td><td>01 06 00 01 17 70 D6 1E</td><td>25</td></tr></tbody></table><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;asr.h&quot;</span></span></span><br><span class="line"><span class="keyword">extern</span> <span class="string">&quot;C&quot;</span>&#123; <span class="type">void</span> * __dso_handle = <span class="number">0</span> ;&#125;</span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;setup.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;HardwareSerial.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">uint32_t</span> snid;</span><br><span class="line"><span class="type">void</span> <span class="title function_">ASR_CODE</span><span class="params">()</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">app</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//&#123;speak:小萌-可爱女童,vol:4,speed:12,platform:haohaodada&#125;</span></span><br><span class="line"><span class="comment">//&#123;playid:10001,voice:欢迎使用我的电源&#125;</span></span><br><span class="line"><span class="comment">//&#123;playid:10002,voice:告辞&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*描述该功能...</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">ASR_CODE</span><span class="params">()</span>&#123;</span><br><span class="line">  set_state_enter_wakeup(<span class="number">15000</span>);</span><br><span class="line">  <span class="comment">//本函数是语音识别成功钩子程序</span></span><br><span class="line">  <span class="comment">//运行时间越短越好，复杂控制启动新线程运行</span></span><br><span class="line">  <span class="comment">//唤醒时间设置必须在ASR_CODE中才有效</span></span><br><span class="line">  <span class="comment">//用switch分支选择，根据不同的识别成功的ID执行相应动作，点击switch左上齿轮</span></span><br><span class="line">  <span class="comment">//可以增加分支项</span></span><br><span class="line">  <span class="keyword">switch</span> (snid) &#123;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x12</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xE8</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x0F</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x12</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x29</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xCF</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x68</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x89</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xB4</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xA4</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x89</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xE1</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x03</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x48</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x89</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x0C</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">5</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x04</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xB0</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x8A</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xBE</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">6</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x04</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xEC</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x8A</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x87</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">7</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x90</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x8A</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">8</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x07</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xD0</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x8A</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x66</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">9</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x09</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x60</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x8F</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xB2</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">10</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x09</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xD8</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x8F</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xC0</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">11</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x0B</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x7C</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x8F</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x1B</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">12</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x0B</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xB8</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x8E</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x88</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">13</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x0E</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x10</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x8C</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x66</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">14</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xC8</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xD9</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x9C</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">15</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xF4</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xD8</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x1D</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">16</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x03</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xE8</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xD8</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xB4</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">17</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x05</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xDC</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xDA</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xC3</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">18</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x07</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xD0</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xDB</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xA6</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">19</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x09</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xC4</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xDF</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xC9</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">20</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x0B</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xB8</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xDF</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x48</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">21</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x0D</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xAC</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xDC</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xE7</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">22</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x0F</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xA0</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xDD</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x82</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">23</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x11</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x94</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xD5</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xF5</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">24</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x13</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x88</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xD5</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x5C</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">   <span class="keyword">case</span> <span class="number">25</span>:</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x06</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x00</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x01</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x17</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x70</span>);</span><br><span class="line">    Serial1.write(<span class="number">0xD6</span>);</span><br><span class="line">    Serial1.write(<span class="number">0x1E</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">app</span><span class="params">()</span>&#123;</span><br><span class="line">  <span class="keyword">while</span> (<span class="number">1</span>) &#123;</span><br><span class="line">    delay(<span class="number">100</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  vTaskDelete(<span class="literal">NULL</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">hardware_init</span><span class="params">()</span>&#123;</span><br><span class="line">  <span class="comment">//需要操作系统启动后初始化的内容</span></span><br><span class="line">  <span class="comment">//音量范围1-7</span></span><br><span class="line">  vol_set(<span class="number">5</span>);</span><br><span class="line">  setPinFun(<span class="number">2</span>,FORTH_FUNCTION);</span><br><span class="line">  setPinFun(<span class="number">3</span>,FORTH_FUNCTION);</span><br><span class="line">  Serial1.begin(<span class="number">115200</span>);</span><br><span class="line">  xTaskCreate(app,<span class="string">&quot;app&quot;</span>,<span class="number">128</span>,<span class="literal">NULL</span>,<span class="number">4</span>,<span class="literal">NULL</span>);</span><br><span class="line">  vTaskDelete(<span class="literal">NULL</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">setup</span><span class="params">()</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="comment">//需要操作系统启动前初始化的内容</span></span><br><span class="line">  <span class="comment">//播报音下拉菜单可以选择，合成音量是指TTS生成文件的音量</span></span><br><span class="line">  <span class="comment">//欢迎词指开机提示音，可以为空</span></span><br><span class="line">  <span class="comment">//退出语音是指休眠时提示音，可以为空</span></span><br><span class="line">  <span class="comment">//&#123;ID:30,keyword:&quot;唤醒词&quot;,ASR:&quot;幺幺零&quot;,ASRTO:&quot;我在&quot;&#125;</span></span><br><span class="line">  <span class="comment">//休眠后用唤醒词唤醒后才能执行命令，唤醒词最多5个。回复语可以空。ID范围为0-9999</span></span><br><span class="line">  <span class="comment">//&#123;ID:0,keyword:&quot;命令词&quot;,ASR:&quot;芝麻开门&quot;,ASRTO:&quot;开了&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:1,keyword:&quot;命令词&quot;,ASR:&quot;芝麻关门&quot;,ASRTO:&quot;关了&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:2,keyword:&quot;命令词&quot;,ASR:&quot;输出电压三点六伏&quot;,ASRTO:&quot;三点六伏&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:3,keyword:&quot;命令词&quot;,ASR:&quot;输出电压四点二伏&quot;,ASRTO:&quot;四点二伏&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:4,keyword:&quot;命令词&quot;,ASR:&quot;输出电压八点四伏&quot;,ASRTO:&quot;八点四伏&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:5,keyword:&quot;命令词&quot;,ASR:&quot;输出电压十二伏&quot;,ASRTO:&quot;十二伏&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:6,keyword:&quot;命令词&quot;,ASR:&quot;输出电压十二点六伏&quot;,ASRTO:&quot;十二点六伏&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:7,keyword:&quot;命令词&quot;,ASR:&quot;输出电压十六点八伏&quot;,ASRTO:&quot;十六点八伏&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:8,keyword:&quot;命令词&quot;,ASR:&quot;输出电压二十伏&quot;,ASRTO:&quot;二十伏&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:9,keyword:&quot;命令词&quot;,ASR:&quot;输出电压二十四伏&quot;,ASRTO:&quot;二十四伏&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:10,keyword:&quot;命令词&quot;,ASR:&quot;输出电压二十五点六伏&quot;,ASRTO:&quot;二十五点六伏&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:11,keyword:&quot;命令词&quot;,ASR:&quot;输出电压二十九点四伏&quot;,ASRTO:&quot;二十九点四伏&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:12,keyword:&quot;命令词&quot;,ASR:&quot;输出电压三十伏&quot;,ASRTO:&quot;三十伏&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:13,keyword:&quot;命令词&quot;,ASR:&quot;输出电压三十六伏&quot;,ASRTO:&quot;三十六伏&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:14,keyword:&quot;命令词&quot;,ASR:&quot;输出电流零点二安&quot;,ASRTO:&quot;零点二安&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:15,keyword:&quot;命令词&quot;,ASR:&quot;输出电流零点五安&quot;,ASRTO:&quot;零点五安&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:16,keyword:&quot;命令词&quot;,ASR:&quot;输出电流一安&quot;,ASRTO:&quot;一安&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:17,keyword:&quot;命令词&quot;,ASR:&quot;输出电流一点五安&quot;,ASRTO:&quot;一点五安&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:18,keyword:&quot;命令词&quot;,ASR:&quot;输出电流两安&quot;,ASRTO:&quot;两安&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:19,keyword:&quot;命令词&quot;,ASR:&quot;输出电流两点五安&quot;,ASRTO:&quot;两点五安&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:20,keyword:&quot;命令词&quot;,ASR:&quot;输出电流三安&quot;,ASRTO:&quot;三安&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:21,keyword:&quot;命令词&quot;,ASR:&quot;输出电流三点五安&quot;,ASRTO:&quot;三点五安&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:22,keyword:&quot;命令词&quot;,ASR:&quot;输出电流四安&quot;,ASRTO:&quot;四安&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:23,keyword:&quot;命令词&quot;,ASR:&quot;输出电流四点五安&quot;,ASRTO:&quot;四点五安&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:24,keyword:&quot;命令词&quot;,ASR:&quot;输出电流五安&quot;,ASRTO:&quot;五安&quot;&#125;</span></span><br><span class="line">  <span class="comment">//&#123;ID:25,keyword:&quot;命令词&quot;,ASR:&quot;输出电流六安&quot;,ASRTO:&quot;六安&quot;&#125;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="结构"><a href="#结构" class="headerlink" title="结构"></a>结构</h3><p>结构还是用的 Solidworks 我也尝试过用 freeCAD，但是易用性实在不好，我实在不想学了。</p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/PC-SK150/SK150.png" alt="SK150"></p><p>因为要增加一块小板，还有喇叭麦克风，所以在之前的基础上增加了长度，另外开始是想做一个喇叭腔体放左边的，后来发现声音已经够大了，所以就懒得折腾了，不过位置还是留着了。<br>源文件下载：</p><p>SK150.SLDPRT 链接: <a href="https://pan.baidu.com/s/1cP8gwmTejCkrd9v1A6fcKQ?pwd=tuu8">https://pan.baidu.com/s/1cP8gwmTejCkrd9v1A6fcKQ?pwd=tuu8</a> 提取码: tuu8</p><p>SK150.STEP 链接: <a href="https://pan.baidu.com/s/1yjHJnCcTu9I7EgCVFSPhNw?pwd=2prk">https://pan.baidu.com/s/1yjHJnCcTu9I7EgCVFSPhNw?pwd=2prk</a>  提取码: 2prk</p><h3 id="装配"><a href="#装配" class="headerlink" title="装配"></a>装配</h3><p>装配完成图片：<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/PC-SK150/power2.jpg" alt="power2"><br>打印有点瑕疵，不过颜色搭得上了。语音功能正常，我也不录视频了，麻烦。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;上次用 SK150 模块改装了一台电源：&lt;a href=&quot;https://mianao.info/repurpose-pc-power-supply-dc-regulator/&quot;&gt;变废为宝-将淘汰的PC电源改装成直流稳压源&lt;/a&gt;，一直想升级一下。为什么呢，一个原因是之前打</summary>
      
    
    
    
    <category term="DIY" scheme="https://mianao.info/categories/DIY/"/>
    
    
    <category term="PC" scheme="https://mianao.info/tags/PC/"/>
    
  </entry>
  
  <entry>
    <title>解决小狼毫输入法重启电脑需要手动部署的问题</title>
    <link href="https://mianao.info/fix-weasel-input-method-manual-deploy-on-boot/"/>
    <id>https://mianao.info/fix-weasel-input-method-manual-deploy-on-boot/</id>
    <published>2026-01-07T16:00:00.000Z</published>
    <updated>2026-01-07T16:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>使用小狼毫，雾凇拼音很久了，<a href="https://mianao.info/3990756e/">Rime输入法小狼毫简明使用手册</a>，其他都很满意，但一直以来就有一个问题困扰我：重启电脑后，<code>weasel.yaml</code> 或 <code>weasel.custom.yaml</code> 等文件里改的配置参数非得重新手动部署一下才有效，点击 <code>build</code> 里的 <code>weasel.yaml</code> 查看修改的设置又都显示正常。</p><p>开始我还搜索了一下，有些说是小狼毫输入法的 bug，但都是老版本，后面 0.15.0,0.16.0,到现在 0.17.4 了，我的四台电脑都是这样。<br>后来就放弃了，每次重启电脑就再手动部署一遍。这两天电脑重启了多了点，我就有些不爽，想问 AI 把这个问题解决了。<br>问 ChatGPT，先是怀疑我的多个 YMAL 设置不对，格式不对，又是让我查日志，折腾了半天还是不知道问题出在哪。<br>我改问 Gemini，几轮对话下来还真找到问题出在哪了。</p><h3 id="解决问题"><a href="#解决问题" class="headerlink" title="解决问题"></a>解决问题</h3><h4 id="确认问题"><a href="#确认问题" class="headerlink" title="确认问题"></a>确认问题</h4><ol><li><p>按下 <code>Win + R</code>，输入 <code>shell:startup</code> 并回车。</p></li><li><p>在打开的文件夹里，右键 -&gt; 新建 -&gt; 快捷方式。</p></li><li><p>对象位置输入： <code>&quot;C:\Program Files\Rime\weasel-0.17.4\WeaselServer.exe&quot;</code> (注意确认安装路径，并要带双引号)。</p></li><li><p>右键点击刚才创建的这个快捷方式 -&gt; 属性。在“目标”栏的末尾添加一个空格和参数 <code>/p</code>。<br>变成这样：<code>...WeaselServer.exe&quot; /p</code><br><code>/p</code> 参数会告诉小狼毫这是由用户启动的常驻进程。<br>保存后重启电脑。不要动任何设置，再进入<code>启动</code>文件夹里，双击运行。看看是不是不用重新部署小狼毫，设置的参数都生效了，如果是，就按下面的步骤解决问题。如果不是，那就和我的问题不一样，去检查一下 <code>build</code> 里的 <code>weasel.yaml</code> 文件。</p></li></ol><h4 id="核心原因分析（AI）"><a href="#核心原因分析（AI）" class="headerlink" title="核心原因分析（AI）"></a>核心原因分析（AI）</h4><ol><li><p>启动冲突： 那两个开关对应着系统层面的自启动逻辑。当你重启电脑时，系统自带的启动项（可能是旧版残留或安装程序注册的）抢先运行了 WeaselServer。</p></li><li><p>环境变量滞后： 系统自带的启动项由于加载优先级太高（启动太早），此时 Windows 还没准备好你的 AppData 用户路径，导致这个抢先运行的进程变成了“聋子”，读不到配置。</p></li><li><p>快捷方式失效： 当你登录桌面后，shell:startup 里的快捷方式尝试运行，但发现后台已经有一个 WeaselServer 了，于是它就自动退出了（小狼毫是单实例程序），没能成功“拨乱反正”。</p></li><li><p>手动双击有效： 你手动双击时，虽然进程已存在，但手动触发会向原进程发送信号或强制刷新环境，从而激活了配置。</p></li></ol><h4 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h4><ol><li>打开 Windows 设置 -&gt; 账户 -&gt;  和 设置 -&gt; 应用 -&gt; 按下图关闭。</li></ol><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Rime/login.png" alt="login"></p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Rime/backup.png" alt="backup"></p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Rime/startup.png" alt="startup"></p><ol start="2"><li>打开任务管理器 -&gt; 启动，<code>WeaselServer.exe</code> 即小狼毫算法服务禁用。</li></ol><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/Rime/taskmgr.png" alt="taskmgr"></p><ol start="3"><li><p>按 <code>Win + R</code> 输入 <code>regedit</code> 打开 注册表编辑器。<br>定位到：<code>HKEY_CURRENT_USER\Software\Rime\Weasel</code><br>在右侧空白处右键 -&gt; 新建 -&gt; DWORD (32位) 值。命名为 <code>Preload</code>，并将其值设置为 <code>1</code>（十六进制）。原理： 这是一个隐藏开关，告诉小狼毫在服务启动时更积极地预载用户配置。</p></li><li><p>在注册表里依次检查以下三个路径：</p></li></ol><ul><li><p><code>HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run</code></p></li><li><p><code>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run</code></p></li><li><p><code>HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run</code></p></li></ul><p>如果看到任何 <code>Weasel</code> 或 <code>Rime</code> 的条目，全部右键删除。</p><ol start="5"><li>重启电脑。小狼毫应该就正常运行了所有配置，不用再部署一次了。</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h3&gt;&lt;p&gt;使用小狼毫，雾凇拼音很久了，&lt;a href=&quot;https://mianao.info/3990756e/&quot;&gt;Rime输入法小狼毫简明使用手册</summary>
      
    
    
    
    <category term="Soft" scheme="https://mianao.info/categories/Soft/"/>
    
    
    <category term="RIME" scheme="https://mianao.info/tags/RIME/"/>
    
  </entry>
  
  <entry>
    <title>KiCad常用插件推荐</title>
    <link href="https://mianao.info/kicad-tool-recommend/"/>
    <id>https://mianao.info/kicad-tool-recommend/</id>
    <published>2025-11-30T16:00:00.000Z</published>
    <updated>2026-03-20T16:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>在上篇我写了 <a href="https://mianao.info/eda-pcb-software-overview-2025/">盘点 EDA&#x2F;PCB 常用软件现状</a>，重点推荐了 KiCad 这款开源的 PCB 设计软件。<br>现在我推荐一些比较好用的插件，因为使用时间还比较短，试用数量不多，后续有好用的我会持续更新。</p><h3 id="重点推荐"><a href="#重点推荐" class="headerlink" title="重点推荐"></a>重点推荐</h3><p><strong>我修改的插件库安装链接：</strong><br><a href="https://github.com/harry10086/kicad-repository">https://github.com/harry10086/kicad-repository</a></p><p>目前有四个插件：<br><strong>ViaStitching:</strong> Automatic via stitching generation for PCB zones (自动添加GND缝合孔 中文版)<br><strong>Interactive HTML BOM:</strong> Interactive BOM generator with multi-CAD support (HTML BOM 生成工具中文版 适合手焊)<br><strong>KiCAD JLCPCB Tools:</strong> 嘉立创 PCB SMT 工具<br><strong>JLCImport:</strong> JLCPCB&#x2F;立创商城库导入工具</p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/plugins.png" alt="plugins"></p><p>在扩展管理器里面添加库链接即可。<br>repository URL: <code>https://raw.githubusercontent.com/harry10086/kicad-repository/main/repository.json</code></p><p><strong>立创商城封装库转 KiCad 库：EasyKiConverter</strong><br>开源地址：<a href="https://github.com/tangsangsimida/EasyKiConverter_QT">https://github.com/tangsangsimida/EasyKiConverter_QT</a><br>一个强大的 Python 工具，用于将嘉立创（LCSC）和 EasyEDA 元件转换为 KiCad 格式，支持符号、封装和 3D 模型的完整转换。提供现代化的 PyQt6 桌面界面，让元件转换变得简单高效。</p><ul><li>符号转换：将 EasyEDA 符号转换为 KiCad 符号库（.kicad_sym）</li><li>封装生成：从 EasyEDA 封装创建 KiCad 封装（.kicad_mod）</li><li>3D模型支持：自动下载并转换 3D 模型（支持多种格式）</li><li>批量处理：支持多个元件同时转换</li><li>网络重试机制：网络请求失败时自动重试，提高转换成功率<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/EasyKiConverter.webp" alt="EasyKiConverter"></li></ul><p><strong>KiCAD JLCImport</strong><br>lcsc.com 元件搜索，导入 easyeda.com 封装到 KiCAD 库，可选择导入项目库或自定义库。<br>有 plugin, CLI, GUI, TUI 四种工作形式。<br>原地址：<a href="https://github.com/jvanderberg/kicad_jlcimport">https://github.com/jvanderberg/kicad_jlcimport</a></p><p>我 fork 后改版的地址：<a href="https://github.com/harry10086/kicad_jlcimport">https://github.com/harry10086/kicad_jlcimport</a></p><ul><li>搜索源换成 szlcsc.com；</li><li>增加了符号和封装预览；</li></ul><p><strong>JLCImport.zip 百度云链接:</strong> <a href="https://pan.baidu.com/s/1uk-77LpDFXMov0UyXq4dag?pwd=9sfg">https://pan.baidu.com/s/1uk-77LpDFXMov0UyXq4dag?pwd=9sfg</a><br>提取码: 9sfg</p><p><strong>JLCImport.zip 夸克网盘链接：</strong><a href="https://pan.quark.cn/s/3d432d330663">https://pan.quark.cn/s/3d432d330663</a></p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://github.com/harry10086/kicad_jlcimport/raw/main/images/search.png" alt="alt text"></p><p><strong>KiCad LCSC Manager</strong><br>同样是立创商城的元件库搜索，但只能导入项目库，我提了个需求，不知道后面会不会增加自定义库。<br>下载地址：<br><a href="https://github.com/hulryung/kicad-lcsc-manager">https://github.com/hulryung/kicad-lcsc-manager</a></p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.com/hulryung/kicad-lcsc-manager/raw/main/docs/images/screenshot-main-dialog.png" alt="alt text"></p><p><strong>JLCPCB-KiCad-Library</strong><br>这是一个完整的立创商城 KiCad 元件库，包含匹配的原理图符号和 PCB 封装，以及完整的 3D STEP 模型。该元件库主要使用 JLCPCB 基本元件和推荐元件清单中的元件。每日会更新的库存和价格信息，但是安装版本只到去年7月，如果下载每天更新的压缩包，元器件就有缺失，我没找到解决方法。<br>开源地址：<br><a href="https://github.com/CDFER/JLCPCB-Kicad-Library">https://github.com/CDFER/JLCPCB-Kicad-Library</a></p><p><strong>KiCAD JLCPCB tools</strong><br>这是一个嘉立创打板和贴片的一键工具，生成嘉立创可用的 gerber 文件和 BOM，坐标文件。<br>原地址：<a href="https://github.com/Bouni/kicad-jlcpcb-tools">https://github.com/Bouni/kicad-jlcpcb-tools</a></p><p>原插件运行即下载国外的 lcsc.com 数据库文件，压缩包大小近 2G，需要可靠的网络，耗时比较久。<br>好处就是搜索特别快，因为数据库在本地，坏处是价格，库存都是国外的，而且实时性不好，数据打包依赖国外开发者。<br>我 fork 后改版的地址：<a href="https://github.com/harry10086/kicad-jlcpcb-tools">https://github.com/harry10086/kicad-jlcpcb-tools</a></p><ul><li>汉化了大多数按钮；</li><li>从立创商城实时搜索元器件，会有一点延时，而且一次加载数量不能太多，国内网站反爬虫很厉害；</li><li>减少了元件过滤规则；</li><li>元件详情页可以复制；</li></ul><p><strong>KiCAD-JLCPCB-tools 国内版.zip 百度云链接:</strong> <a href="https://pan.baidu.com/s/1uk-77LpDFXMov0UyXq4dag?pwd=9sfg">https://pan.baidu.com/s/1uk-77LpDFXMov0UyXq4dag?pwd=9sfg</a><br>提取码: 9sfg</p><p><strong>KiCAD-JLCPCB-tools 国内版 夸克网盘链接：</strong><a href="https://pan.quark.cn/s/49af8e59def2">https://pan.quark.cn/s/49af8e59def2</a></p><p><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/jlcpcb-tools-zh.png" alt="jlcpcb-tools-zh"></p><h3 id="插件推荐"><a href="#插件推荐" class="headerlink" title="插件推荐"></a>插件推荐</h3><ol><li><p>Interactive Html Bom<br>开源地址：<a href="https://github.com/openscopeproject/InteractiveHtmlBom">Interactive Html Bom</a><br>该插件主要功能是生成 BOM 列表，能够直观关联并轻松搜索元件及其在 PCB 上的位置。手工焊接时非常好用，可以快速找到电路板上元件的位置，并且进行标注是否焊接完成。当然也可以通过点击板图上的封装来反向查找元件。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/bom.webp" alt="bom"></p></li><li><p>Fabrication Toolkit<br>开源地址：<a href="https://github.com/bennymeg/Fabrication-Toolkit">https://github.com/bennymeg/Fabrication-Toolkit</a><br>这个插件是支持嘉立创的 PCB 加工，可以出 gerber，可以出关联立创商城编码的 BOM 等。</p></li><li><p>KiCad Via Patterns<br>开源地址：<a href="https://github.com/adamws/kicad-via-patterns">https://github.com/adamws/kicad-via-patterns</a><br>按规则排列走线过孔，更加整齐。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.com/adamws/kicad-via-patterns/raw/master/resources/stagger.png" alt="alt text"></p></li><li><p>kicad-action-scripts<br>开源地址：<a href="https://github.com/jsreynaud/kicad-action-scripts">https://github.com/jsreynaud/kicad-action-scripts</a><br>对特定网络批量打缝合孔，可设置过孔大小，间隔等。</p></li><li><p>Archive 3D models<br>开源地址：<a href="https://github.com/MitjaNemec/Archive3DModels">https://github.com/MitjaNemec/Archive3DModels</a><br>该插件将 3D 模型保存到项目的子文件夹，自动更新 PCB 封装的指向链接，方便归档，分享。</p></li><li><p>PlaceFootprints<br>开源地址：<a href="https://github.com/MitjaNemec/PlaceFootprints">https://github.com/MitjaNemec/PlaceFootprints</a><br>在 PCB 里布局时，按几何规则来摆放元器件，比如圆形，矩阵等。</p></li><li><p>Replicate Layout<br>开源地址：<a href="https://github.com/MitjaNemec/ReplicateLayout">https://github.com/MitjaNemec/ReplicateLayout</a><br>复制相同电路的布局，包括元件，走线，文本等。</p></li><li><p>Freerouting<br>开源地址：<a href="https://github.com/freerouting/freerouting">https://github.com/freerouting/freerouting</a><br>自动布线，我试了一个比较简单的双面板，差强人意，电路入门者可以玩一玩。</p></li><li><p>RF-Tools<br>开源地址：<a href="https://github.com/easyw/RF-tools-KiCAD">https://github.com/easyw/RF-tools-KiCAD</a><br>射频 PCB 设计工具，比如走线，过孔，阻焊层的一些快速工具。</p></li><li><p>Place_By_Sch_KiCad<br>开源地址：<a href="https://github.com/sagarHackeD/Place_By_Sch_KiCad">https://github.com/sagarHackeD/Place_By_Sch_KiCad</a><br>根据原理图的元件位置，自动进行 PCB 布局。</p></li><li><p>KiCad-Parasitics<br>开源地址：<a href="https://github.com/Steffen-W/KiCad-Parasitics">https://github.com/Steffen-W/KiCad-Parasitics</a><br>测量 PCB 中的寄生参数。</p></li><li><p>kicad-coil-creator<br>开源地址：<a href="https://github.com/DIaLOGIKa-GmbH/kicad-coil-creator">https://github.com/DIaLOGIKa-GmbH/kicad-coil-creator</a><br>直接画 PCB 走线圆形线圈，如 NFC 天线。不过目前还不支持矩形线圈。</p></li><li><p>pcb2blender<br>开源地址：<a href="https://github.com/30350n/pcb2blender">https://github.com/30350n/pcb2blender</a><br>将 PCB 的 3D 模型导出为 <code>.pcb3d</code> 格式，再导入 Blender 进行渲染或其他处理。 </p></li><li><p>Space-Filling-Curve<br>开源地址：<a href="https://github.com/HaydenHu/Space-Filling-Curve">https://github.com/HaydenHu/Space-Filling-Curve</a><br>用 Hilbert, Moore, Peano 曲线填充圆形或矩形空间。</p></li><li><p>PosOrient<br>开源地址：<a href="https://github.com/jacmie/PosOrient">https://github.com/jacmie/PosOrient</a><br>将所有元件列表定位坐标，旋转角度等，方便统一更改布局。</p></li><li><p>kicad-testpoints-pcm<br>开源地址：<a href="https://github.com/TheJigsApp/kicad-testpoints-pcm">https://github.com/TheJigsApp/kicad-testpoints-pcm</a><br>将任意焊盘标记为测试点，并可修改其属性。</p></li><li><p>Dial Scale FrontPanel Footprint Wizard<br>开源地址：<a href="https://github.com/BatNoize/Dial-Scale-FrontPanel-Footprint-Wizard">https://github.com/BatNoize/Dial-Scale-FrontPanel-Footprint-Wizard</a><br>一个刻度盘封装向导。</p></li><li><p><del>TextPostionSize</del><br>开源地址：<a href="https://github.com/HaydenHu/kicad-text-postion-size">https://github.com/HaydenHu/kicad-text-postion-size</a><br>批量更改 PCB 中文本大小和位置。<br>我发现这个不用插件也可以实现，KiCAD 本身都有的功能。</p></li><li><p>pcb-action-tools<br>开源地址：<a href="https://github.com/easyw/kicad-action-tools">https://github.com/easyw/kicad-action-tools</a><br>多个 PCB 工具，比如检查孔环，3D 模型封装缺失，移动图形到指定层，导出贴片坐标文件，移动封装对齐网格等。</p></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h3&gt;&lt;p&gt;在上篇我写了 &lt;a href=&quot;https://mianao.info/eda-pcb-software-overview-2025/&quot;&gt;盘</summary>
      
    
    
    
    <category term="Work" scheme="https://mianao.info/categories/Work/"/>
    
    
    <category term="EDA" scheme="https://mianao.info/tags/EDA/"/>
    
    <category term="KiCAD" scheme="https://mianao.info/tags/KiCAD/"/>
    
  </entry>
  
  <entry>
    <title>盘点 EDA/PCB 常用软件现状</title>
    <link href="https://mianao.info/eda-pcb-software-overview-2025/"/>
    <id>https://mianao.info/eda-pcb-software-overview-2025/</id>
    <published>2025-11-05T16:00:00.000Z</published>
    <updated>2025-11-05T16:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>从业电子硬件二十多年，现在也老眼昏花了，0402 的焊接基本看不见了，4 层板还能凑合能画一下，复杂的电路根本干不了了，岁月不饶人啊。<br>第一个学会的 EDA 软件是 Protel 99 SE，记得当时应该是 Protel 公司对高校免费授权，所以我们都学了这个，工作后记得第一块电源板就是 Protel 画的。<br>后来发现深圳的很多中小型公司都用的是 PADS，我就转到了 PADS，这些年主要还是在用 PADS 画原理图 PCB 板。期间在中兴和华为也学了 Cadence，但我只学会了用 OrCAD 画原理图，因为他们都有专门的 Layout 工程师，当然板也是太复杂了。<br>二十多年过去了，这几大软件现状如何？</p><h3 id="龙头-Cadence"><a href="#龙头-Cadence" class="headerlink" title="龙头 Cadence"></a>龙头 Cadence</h3><p>Cadence 在 PCB 设计领域一直都是龙头，大型企业的 EDA 工具必须是 Cadence。对于复杂的电路设计，Cadence 应该是最合适的设计软件。<br>而且，这么多年过去，Cadence 的地位一直无人撼动。<br>Cadence 的原理图设计 OrCAD 还是比较容易上手的，很快能学会，易用性也好。Allegro 学习曲线就比较陡了，我曾经学过一段时间，但是后来工作接触的板都没那么复杂，用 PADS 画起来更快一点。而复杂的板也不用我画，所以至今不会，以后应该也不会了。</p><h3 id="老二-Altium-Designer"><a href="#老二-Altium-Designer" class="headerlink" title="老二 Altium Designer"></a>老二 Altium Designer</h3><p>Altium Designer 我为什么排老二，因为绝大多数高校都教的是 Altium Designer，上手和易用性都很不错，有很好的群众基础。<br>Altium Designer 给我的印象就是吃配置，大约十年前用过一段时间，有时候反应慢了一些，基本没用过 3D 功能。当然这也不是大问题，最大的问题是公司会被律师发函，打电话。大大小小的公司我也见过很多了，真没有遇见过哪一家是买了 Altium Designer 的授权的。一般中小公司真要用就电脑不联网，大公司不会用。<br>所以，2024 年 Altium Designer 卖给了日本瑞萨电子，有什么变化呢？Altium Designer 25.8 是最后一个版本，后面就没有了，变成 Altium Agile 26 了。</p><h3 id="并列老二-PADS"><a href="#并列老二-PADS" class="headerlink" title="并列老二 PADS"></a>并列老二 PADS</h3><p>PADS 来自于美国公司 Mentor Graphics，2017 年被西门子收购了，后来就没有 Mentor 了，变成了 Siemens EDA。2024 年 1 月，成都派兹互连独家收购西门子 EDA 的 PADS Standard 和 PADS Standard Plus 软件的源代码及中国区业务，而且派兹互连对软件源代码拥有永久开发权。<br>PADS 是我使用最久的 PCB 设计软件，从 04 年开始学，至今二十多年了，当然很大一部分时间都是用的破解版，只有某一家公司要上市才买了授权。主要原因也是因为 PADS 很少发律师函，打电话，我也是今年才第一次接到电话。<br>PADS 在珠三角用的非常多，我觉得应该是以前港台企业带来的，那时候他们都是方案商。当然也是因为上手容易，学起来快，消费类电子的板基本上都能胜任，又没人发律师函，中小型企业非常适用。<br>成都派兹发布了 SailWind 3.0，加了 AI，自动化走线等，自动布局好像都有改善，可以免费试用，不过我现在不想试了。</p><h3 id="国产新秀-立创-EDA"><a href="#国产新秀-立创-EDA" class="headerlink" title="国产新秀 立创 EDA"></a>国产新秀 立创 EDA</h3><p> 立创 EDA 也就是 <a href="https://lceda.cn/">EasyEDA</a>，嘉立创旗下的 EDA 软件。说是新秀，其实也开发很多年了，有安装版还有 web 版，用 EasyEDA 软件画板每个月可以在嘉立创免费打样两次。立创商城也和 EasyEDA 的库融合到了一起，库和 BOM 还是非常方便的。<br>立创还有开源硬件平台，我看很热闹，再加上嘉立创搞的结构加工，SMT，基本上实现了一条龙。采购元器件，PCB 打样非常快。<br>我安装了 EasyEDA 专业版，能用，但总感觉差点意思，无论是原理图还是 PCB，效率不够高，菜单界面，快捷键等，总是觉得设计速度上不来，PADS 三天可以搞定的，EasyEDA 我画了一周还不行。当然你可以说是我 PADS 用习惯了，但我后面试用了 KiCad，同样是从头学，边搜索边画，KiCad 的易用性，成熟度还是更高一些。<br>所以，我说：</p><h3 id="开源王者-KiCad"><a href="#开源王者-KiCad" class="headerlink" title="开源王者 KiCad"></a>开源王者 KiCad</h3><p>KiCad 是款全平台的开源 EDA 软件：<a href="https://gitlab.com/kicad/code/kicad">https://gitlab.com/kicad/code/kicad</a>。大概十年前我也试用过 KiCad，当时的感觉只能说凑合，很快就卸载了，继续 PADS。<br>现在 KiCad 已经是 9.0 了，上周边学边画了两块板，已经很不错了，完全满足了我目前的需求。<br>这个公司开源了很多板，全都是用的 KiCad：<a href="https://github.com/antmicro/dc-scm-osm-l-carrier-card">https://github.com/antmicro/dc-scm-osm-l-carrier-card</a>，看得出来，一般公司使用 KiCad 完全没有问题了。<br>我来简单介绍一下吧，建一个工程：<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/Project.webp" alt="Project"><br>打开原理图：<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/SCH.webp" alt="SCH"><br>F8 更新元件到 PCB：<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/PCB.webp" alt="PCB"><br>KiCad 的插件，库都很多，比如这个 BOM 插件，我非常喜欢，自己焊接调试时非常好用：<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/bom.webp" alt="bom"><br>在搜索的过程中，我发现华强 PCB 也就是现在的华秋，基于 KiCad 发布了一个华秋板，主要加了华秋的库和 AI。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/HQPCB.webp" alt="HQPCB"><br>库里还有做好的复用模块，不过用起来还得自己好好检查一下，比如图里第一个就应该是 PMOS。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/MUX.webp" alt="MUX"><br>AI 问答我感觉不咋的，但可以直接发图自动画封装，这就很快了。<br><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-lazy-src="https://github.mianao.info/https://raw.githubusercontent.com/harry10086/picx-images-hosting/master/EDA/AI.webp" alt="AI"><br>KiCon Asia 2025 大会即将在深圳举办了，合作方就是华秋，看来这两年 KiCad 变化这么大，背后还是有一些企业在出力的。<br>于是，我也捐了五十块钱。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h3&gt;&lt;p&gt;从业电子硬件二十多年，现在也老眼昏花了，0402 的焊接基本看不见了，4 层板还能凑合能画一下，复杂的电路根本干不了了，岁月不饶人啊。&lt;br</summary>
      
    
    
    
    <category term="Work" scheme="https://mianao.info/categories/Work/"/>
    
    
    <category term="EDA" scheme="https://mianao.info/tags/EDA/"/>
    
  </entry>
  
</feed>
