解决小狼毫输入法重启电脑需要手动部署的问题

前言 使用小狼毫,雾凇拼音很久了,Rime输入法小狼毫简明使用手册,其他都很满意,但一直以来就有一个问题困扰我:重启电脑后,weasel.yaml 或 weasel.custom.yaml 等文件里改的配置参数非得重新手动部署一下才有效,点击 build 里的 weasel.yaml 查看修改的设置又都显示正常。 开始我还搜索了一下,有些说是小狼毫输入法的 bug,但都是老版本,后面 0.15.0,0.16.0,到现在 0.17.4 了,我的四台电脑都是这样。 后来就放弃了,每次重启电脑就再手动部署一遍。这两天电脑重启了多了点,我就有些不爽,想问 AI 把这个问题解决了。 问 ChatGPT,先是怀疑我的多个 YMAL 设置不对,格式不对,又是让我查日志,折腾了半天还是不知道问题出在哪。 我改问 Gemini,几轮对话下来还真找到问题出在哪了。 解决问题 确认问题 按下 Win + R,输入 shell:startup 并回车。 在打开的文件夹里,右键 -> 新建 -> 快捷方式。 对象位置输入: "C:\Program Files\Rime\weasel-0.17.4\WeaselServer.exe" (注意确认安装路径,并要带双引号)。 右键点击刚才创建的这个快捷方式 -> 属性。在“目标”栏的末尾添加一个空格和参数 /p。 变成这样:...WeaselServer.exe" /p /p 参数会告诉小狼毫这是由用户启动的常驻进程。 保存后重启电脑。不要动任何设置,再进入启动文件夹里,双击运行。看看是不是不用重新部署小狼毫,设置的参数都生效了,如果是,就按下面的步骤解决问题。如果不是,那就和我的问题不一样,去检查一下 build 里的 weasel.yaml 文件。 核心原因分析(AI) 启动冲突: 那两个开关对应着系统层面的自启动逻辑。当你重启电脑时,系统自带的启动项(可能是旧版残留或安装程序注册的)抢先运行了 WeaselServer。 环境变量滞后: 系统自带的启动项由于加载优先级太高(启动太早),此时 Windows 还没准备好你的 AppData 用户路径,导致这个抢先运行的进程变成了“聋子”,读不到配置。 快捷方式失效: 当你登录桌面后,shell:startup 里的快捷方式尝试运行,但发现后台已经有一个 WeaselServer 了,于是它就自动退出了(小狼毫是单实例程序),没能成功“拨乱反正”。 手动双击有效: 你手动双击时,虽然进程已存在,但手动触发会向原进程发送信号或强制刷新环境,从而激活了配置。 解决方法 打开 Windows 设置 -> 账户 -> 和 设置 -> 应用 -> 按下图关闭。 打开任务管理器 -> 启动,WeaselServer.exe 即小狼毫算法服务禁用。 按 Win + R 输入 regedit 打开 注册表编辑器。 定位到:HKEY_CURRENT_USER\Software\Rime\Weasel 在右侧空白处右键 -> 新建 -> DWORD (32位) 值。命名为 Preload,并将其值设置为 1(十六进制)。原理: 这是一个隐藏开关,告诉小狼毫在服务启动时更积极地预载用户配置。 在注册表里依次检查以下三个路径: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run 如果看到任何 Weasel 或 Rime 的条目,全部右键删除。 重启电脑。小狼毫应该就正常运行了所有配置,不用再部署一次了。

2026/1/8
articleCard.readMore

KiCad常用插件推荐

前言 在上篇我写了 盘点 EDA/PCB 常用软件现状,重点推荐了 KiCad 这款开源的 PCB 设计软件。 现在我推荐一些比较好用的插件,因为使用时间还比较短,试用数量不多,后续有好用的我会持续更新。 重点推荐 立创商城封装库转 KiCad 库:EasyKiConverter 开源地址:https://github.com/tangsangsimida/EasyKiConverter_QT 一个强大的 Python 工具,用于将嘉立创(LCSC)和 EasyEDA 元件转换为 KiCad 格式,支持符号、封装和 3D 模型的完整转换。提供现代化的 PyQt6 桌面界面,让元件转换变得简单高效。 符号转换:将 EasyEDA 符号转换为 KiCad 符号库(.kicad_sym) 封装生成:从 EasyEDA 封装创建 KiCad 封装(.kicad_mod) 3D模型支持:自动下载并转换 3D 模型(支持多种格式) 批量处理:支持多个元件同时转换 网络重试机制:网络请求失败时自动重试,提高转换成功率 KiCad 华秋发行版: 下载地址:https://kicad.eda.cn/download KiCad 华秋版主要是增加了云端库,AI 助手和插件库镜像地址。其实 AI 助手没啥特别的智能,只有自动从图片转换为封装值得一试。 插件推荐 Interactive Html Bom 开源地址:Interactive Html Bom 该插件主要功能是生成 BOM 列表,能够直观关联并轻松搜索元件及其在 PCB 上的位置。手工焊接时非常好用,可以快速找到电路板上元件的位置,并且进行标注是否焊接完成。当然也可以通过点击板图上的封装来反向查找元件。 Fabrication Toolkit 开源地址:https://github.com/bennymeg/Fabrication-Toolkit 这个插件是支持嘉立创的 PCB 加工,可以出 gerber,可以出关联立创商城编码的 BOM 等。 KiCad Via Patterns 开源地址:https://github.com/adamws/kicad-via-patterns 按规则排列走线过孔,更加整齐。 kicad-action-scripts 开源地址:https://github.com/jsreynaud/kicad-action-scripts 对特定网络批量打缝合孔,可设置过孔大小,间隔等。 Archive 3D models 开源地址:https://github.com/MitjaNemec/Archive3DModels 该插件将 3D 模型保存到项目的子文件夹,自动更新 PCB 封装的指向链接,方便归档,分享。 PlaceFootprints 开源地址:https://github.com/MitjaNemec/PlaceFootprints 在 PCB 里布局时,按几何规则来摆放元器件,比如圆形,矩阵等。 Replicate Layout 开源地址:https://github.com/MitjaNemec/ReplicateLayout 复制相同电路的布局,包括元件,走线,文本等。 Freerouting 开源地址:https://github.com/freerouting/freerouting 自动布线,我基本不用,不知道智能化怎么样。 RF-Tools 开源地址:https://github.com/easyw/RF-tools-KiCAD 射频 PCB 设计工具,比如走线,过孔,阻焊层的一些快速工具。 Place_By_Sch_KiCad 开源地址:https://github.com/sagarHackeD/Place_By_Sch_KiCad 根据原理图的元件位置,自动进行 PCB 布局。 KiCad-Parasitics 开源地址:https://github.com/Steffen-W/KiCad-Parasitics 测量 PCB 中的寄生参数。 kicad-coil-creator 开源地址:https://github.com/DIaLOGIKa-GmbH/kicad-coil-creator 直接画 PCB 走线圆形线圈,如 NFC 天线。不过目前还不支持矩形线圈。 pcb2blender 开源地址:https://github.com/30350n/pcb2blender 将 PCB 的 3D 模型导出为 .pcb3d 格式,再导入 Blender 进行渲染或其他处理。 Space-Filling-Curve 开源地址:https://github.com/HaydenHu/Space-Filling-Curve 用 Hilbert, Moore, Peano 曲线填充圆形或矩形空间。 PosOrient 开源地址:https://github.com/jacmie/PosOrient 将所有元件列表定位坐标,旋转角度等,方便统一更改布局。 kicad-testpoints-pcm 开源地址:https://github.com/TheJigsApp/kicad-testpoints-pcm 将任意焊盘标记为测试点,并可修改其属性。 Dial Scale FrontPanel Footprint Wizard 开源地址:https://github.com/BatNoize/Dial-Scale-FrontPanel-Footprint-Wizard 一个刻度盘封装向导。 TextPostionSize 开源地址:https://github.com/HaydenHu/kicad-text-postion-size 批量更改 PCB 中文本大小和位置。 pcb-action-tools 开源地址:https://github.com/easyw/kicad-action-tools 多个 PCB 工具,比如检查孔环,3D 模型封装缺失,移动图形到指定层,导出贴片坐标文件,移动封装对齐网格等。

2025/12/1
articleCard.readMore

盘点 EDA/PCB 常用软件现状

前言 从业电子硬件二十多年,现在也老眼昏花了,0402 的焊接基本看不见了,4 层板还能凑合能画一下,复杂的电路根本干不了了,岁月不饶人啊。 第一个学会的 EDA 软件是 Protel 99 SE,记得当时应该是 Protel 公司对高校免费授权,所以我们都学了这个,工作后记得第一块电源板就是 Protel 画的。 后来发现深圳的很多中小型公司都用的是 PADS,我就转到了 PADS,这些年主要还是在用 PADS 画原理图 PCB 板。期间在中兴和华为也学了 Cadence,但我只学会了用 OrCAD 画原理图,因为他们都有专门的 Layout 工程师,当然板也是太复杂了。 二十多年过去了,这几大软件现状如何? 龙头 Cadence Cadence 在 PCB 设计领域一直都是龙头,大型企业的 EDA 工具必须是 Cadence。对于复杂的电路设计,Cadence 应该是最合适的设计软件。 而且,这么多年过去,Cadence 的地位一直无人撼动。 Cadence 的原理图设计 OrCAD 还是比较容易上手的,很快能学会,易用性也好。Allegro 学习曲线就比较陡了,我曾经学过一段时间,但是后来工作接触的板都没那么复杂,用 PADS 画起来更快一点。而复杂的板也不用我画,所以至今不会,以后应该也不会了。 老二 Altium Designer Altium Designer 我为什么排老二,因为绝大多数高校都教的是 Altium Designer,上手和易用性都很不错,有很好的群众基础。 Altium Designer 给我的印象就是吃配置,大约十年前用过一段时间,有时候反应慢了一些,基本没用过 3D 功能。当然这也不是大问题,最大的问题是公司会被律师发函,打电话。大大小小的公司我也见过很多了,真没有遇见过哪一家是买了 Altium Designer 的授权的。一般中小公司真要用就电脑不联网,大公司不会用。 所以,2024 年 Altium Designer 卖给了日本瑞萨电子,有什么变化呢?Altium Designer 25.8 是最后一个版本,后面就没有了,变成 Altium Agile 26 了。 并列老二 PADS PADS 来自于美国公司 Mentor Graphics,2017 年被西门子收购了,后来就没有 Mentor 了,变成了 Siemens EDA。2024 年 1 月,成都派兹互连独家收购西门子 EDA 的 PADS Standard 和 PADS Standard Plus 软件的源代码及中国区业务,而且派兹互连对软件源代码拥有永久开发权。 PADS 是我使用最久的 PCB 设计软件,从 04 年开始学,至今二十多年了,当然很大一部分时间都是用的破解版,只有某一家公司要上市才买了授权。主要原因也是因为 PADS 很少发律师函,打电话,我也是今年才第一次接到电话。 PADS 在珠三角用的非常多,我觉得应该是以前港台企业带来的,那时候他们都是方案商。当然也是因为上手容易,学起来快,消费类电子的板基本上都能胜任,又没人发律师函,中小型企业非常适用。 成都派兹发布了 SailWind 3.0,加了 AI,自动化走线等,自动布局好像都有改善,可以免费试用,不过我现在不想试了。 国产新秀 立创 EDA 立创 EDA 也就是 EasyEDA,嘉立创旗下的 EDA 软件。说是新秀,其实也开发很多年了,有安装版还有 web 版,用 EasyEDA 软件画板每个月可以在嘉立创免费打样两次。立创商城也和 EasyEDA 的库融合到了一起,库和 BOM 还是非常方便的。 立创还有开源硬件平台,我看很热闹,再加上嘉立创搞的结构加工,SMT,基本上实现了一条龙。采购元器件,PCB 打样非常快。 我安装了 EasyEDA 专业版,能用,但总感觉差点意思,无论是原理图还是 PCB,效率不够高,菜单界面,快捷键等,总是觉得设计速度上不来,PADS 三天可以搞定的,EasyEDA 我画了一周还不行。当然你可以说是我 PADS 用习惯了,但我后面试用了 KiCad,同样是从头学,边搜索边画,KiCad 的易用性,成熟度还是更高一些。 所以,我说: 开源王者 KiCad KiCad 是款全平台的开源 EDA 软件:https://gitlab.com/kicad/code/kicad。大概十年前我也试用过 KiCad,当时的感觉只能说凑合,很快就卸载了,继续 PADS。 现在 KiCad 已经是 9.0 了,上周边学边画了两块板,已经很不错了,完全满足了我目前的需求。 这个公司开源了很多板,全都是用的 KiCad:https://github.com/antmicro/dc-scm-osm-l-carrier-card,看得出来,一般公司使用 KiCad 完全没有问题了。 我来简单介绍一下吧,建一个工程: 打开原理图: F8 更新元件到 PCB: KiCad 的插件,库都很多,比如这个 BOM 插件,我非常喜欢,自己焊接调试时非常好用: 在搜索的过程中,我发现华强 PCB 也就是现在的华秋,基于 KiCad 发布了一个华秋板,主要加了华秋的库和 AI。 库里还有做好的复用模块,不过用起来还得自己好好检查一下,比如图里第一个就应该是 PMOS。 AI 问答我感觉不咋的,但可以直接发图自动画封装,这就很快了。 KiCon Asia 2025 大会即将在深圳举办了,合作方就是华秋,看来这两年 KiCad 变化这么大,背后还是有一些企业在出力的。 于是,我也捐了五十块钱。

2025/11/6
articleCard.readMore

2025年的夏天过去了

7 月初的时候,我和小棉袄回了一趟老家。过去好些年我都是过年才回去,也就是偶尔有事才会在其他时间。上次在家过夏天我的记忆还停留在高中的时候,大学的暑假都是在学校,因为路费贵啊,毕业那年,回家没几天就南下了。 这一晃就是二十多年了,2025 年的夏天也悄悄过去了,我有些怀念她。 老房子还立着那,但并不是我小时候住的房子,买的时候我已经上初中了,所以我住这里的时间并没有多少,高中暑假在家的时候,我在堂屋睡午觉,一条菜花蛇在后门口睡觉。上大学之后再没见过了,估计它早死了吧。 家里两只猫天天飞檐走壁,今年或者明年老房子就会拆了,我觉得已经是危房了,但我爸妈还在里面养了头猪。 这里本来是家里的水田,我爸要养龙虾,就找挖机挖了个鱼池,养龙虾,鱼。搞了好几年,每年我爸能弄个几千块钱吧。这是去年拍的照片。 今年国家又拨款改田,小田改大田,于是就回填了,想着以后也没人弄了。 改田后就租给人种了,我看秧长的并不好。 从家门口走出去五十米,就是我的小学,但最早的校舍早就拆除了,楼房是我四年级的时候盖好的,现在村里租给了一个工厂加工豆皮,老师的宿舍也全部卖给了一个村民。 可以看到,六十年代建造的学校房子其实还剩了一片,断壁残垣,梁已经都塌了,瓦落了一地,一排柱子没剩多少了,只有红色的砖墙还立着。记忆中这几间房开始是老师的食堂,供销社,村医务室。后来村里给了一孤寡老书记住,另外部分租给人开酒厂。那老头天天聚集一帮人打上大人,喝茶,我外公就是常客。他最后一天的上午就是在这里,和他的牌友们打了半天牌,中午回去就在树上上吊了。 等到那个老头死了,酒厂也没开下去,酒厂的老板娘出去打工,老板混了几年,离婚,出走,这一片房子就再也没人住过了,任凭风吹雨打,直到现在的模样。 水泥路旁都被人见缝插针的种上了农作物,这边是芝麻,路是村里的,所以谁种就是谁的。 这家屋后种了一些花,我没记错的话应该叫太阳花。 我外公外婆的墓,今年改田后在路上只能看到坟上的草了。我爬了半天过去,四周的旱田都改成了水田,中间几座坟凹在里面,地都是稀的。 这是今年改的大田,以前田埂没这么高,现在他们把梯田推成了平田。 一块块小田连成了一片,稀稀拉拉的秧,癞子一样,租田大户今年全靠国家补贴了。现在基本都是机械化作业了,村里也没几头牛。 堰里的水挺满的,虽然一直干旱。小时候觉得这口堰好大好大,游泳根本游不到头,现在水浅的地方全部填了。它的名字叫妖堰,小时候传说有鬼,我们都很少下去玩,怕被拉走。 刚推平的水田,看得出来没有任何肥力,也旱的开裂了,秧才那么一点点,今年能收点喂鸡都不错了。 水沟两边种满了黄豆,应该是吧,我已经分不清黄豆和绿豆了。 我们管这种野生的瓜叫喜瓜,还没熟。小时候没有零食吃,夏天的棉花地里有很多这种瓜,还没鸡蛋大,但成熟了还是很好吃的。运气好能碰到变异的喜瓜,比鸭蛋还大,吃起来特别开心特别香。 走到我从小成长的地方了,这是我家的山,里面埋着我的爷爷奶奶。山旁边这家是贵州移民,他的地每年往我山这边犁一道,已经侵占了好几米了。我家没人说,他们就得寸进尺了,后面我有时间了回来弄水泥筑一道墙隔着。 从上学前班开始,我就记得邻居家后面的这棵树,名字好像叫榨树。那时候也就碗口粗,两三米高吧。我上学放学常常从它旁边走过,四十年过去了,好像没人砍过它。它自由自在的生长,也没长多少,叶生叶落之间,我已经老了不少。 月亮也还是那个月亮,98 年世界杯的时候,我就是在这样的月色下等待,等待比赛开始。我的爷爷奶奶坐在路边上乘凉,有一句没一句的聊着天。

2025/9/15
articleCard.readMore

京都杂记

趁着小棉袄暑假带她去了一趟日本,主要目的地是京都,总共五天。 我们从深圳出发,过关到香港机场,直飞大阪。上次从香港出发还是去台湾,带着小棉袄从台北到台中再到垦丁,可惜的是,18 年之后就不能台湾自由行了。 在日本进关,最好在 Visit Japan Web 预先填好,这样不用走人工,人脸识别就好了,非常方便快捷。 虽然不会日语,英语也不行,过关没任何障碍,看标识就好了,而且有的日本工作人员会说中文。 从大阪关西机场出来,直接买票坐大巴到京都。 大阪的海很黑,应该是太深了吧,跟我去南海的时候一样。 路过大阪的工厂。 大阪的天空。 App 英语也不行,在日本只能通过手机翻译软件交流,Android 上的微软翻译还挺好用的,在日本全靠它了。 手机网络直接用荣耀的环球行,15 块钱一天 1GB,超过了限速不断网,18 块钱一天 5G,当然还有直接买流量的,比如 1GB(7天有效)15 块钱。看需求吧,反正我一天 1GB 就够了,注意最好一天一天的购买,不然中途想改套餐的时候不能停,不能退,只能浪费了。 流量还可以到了目的地买,也可以一个人开了共享一家人用,就是人不能分开了。 导航就只能是 Google Maps 了。 话说之前小棉袄花了上万学日语,现在基本忘光了,英语补习也花了几万,结果在日本不敢张嘴,天天就知道跟我嘚瑟。 酒店 携程提前预订的京都格兰德酒店,三个人一间,和国内比有点小,价格不算贵吧,五六百人民币一晚。 干净整洁,该有的都有,比如智能马桶。有一个小冰箱,只能冷藏,期间买了一盒冰淇淋,全化了。 还有一个房间里有洗衣机等,我们没这个需求,就没进去研究。 纯净水需要自己去拿,晚了就没有了,一般一人一天一小瓶。 比较遗憾的是没看日本的电视节目,每天回来太累了,洗澡就睡觉了。 大家知道窗户上贴的红色三角是什么意思吗?不光酒店有,有些大厦也有贴,我没发现什么规律,开始还以为是没人的楼梯或房间呢。 京都 到京都时天色已暗,入住后逛了逛附近的商场,随便找了些吃的,味道还挺好。 看到了京都塔。 第二天我们就在京都逛了逛,打算去吃一个说很好吃的拉面,结果到了那里没开门,当天休息。最后随便找了一家吃面,味道也不错,我就喜欢日本台湾的面有硬度等级选项。 第一个感觉就是京都好多寺庙啊,到处都是寺,大的小的,收费的免费的。 小棉袄在路上拍了好多照片,说这是日本漫画里的感觉,最后一天她还让她妈带去买了一套日本漫画。 日本的住宅不光干净整洁,很多外面都有放一些花花草草,有趣的摆件等,感觉好有生活情趣啊。 去了趟二条城,里面不让拍照,很多古董画,也看不懂,房子都是木质的。 京都的天空好漂亮。 什么金阁寺清水寺伏见稻荷大社我们都没去,只去了永观堂。 寺里基本上都是中国人,穿日服拍照的特别多,只碰到一对说日语的情侣,但看他们的跪姿,感觉男的是日本人女的不像。 寺里特别清静,非常适合安安静静的待着,啥也不想,发呆。 这里没有功德箱,更别说二维码了,不过我发现里面的水池也是用水泵抽的,感觉像活水。 国人的习惯非常不好,到哪里垃圾就丢哪里。 这是京都最后一天逛街时发现的,北纬 35度: 京都这样的电动车好多,铃木本田三菱各种牌子的都有,但是我没见过特斯拉或者国产的。 京都的路都不宽,但车流量不小,开车都很规范,所以很少像深圳这样堵车的。当然,我也见过有人实线变道超车的,边开车边看手机的。 超市里好多饮料啊,价格有点贵,好想都来一瓶试试。 超市买的桃子,差不多人民币 20 块钱一个,个头大,但味道没有价格那么震撼。 奈良 第三天我们去了一趟奈良,坐的什么车去的我真说不清楚,因为京都站下的地铁,近铁,JR线,电车,特急,我始终没搞清楚怎么坐,看图都看不懂。 最后通过翻译软件让工作人员教我们坐的,至于票价什么的根本不考虑了。 到奈良很快,路上的风景也很好,真想下次去京都附近的乡下走一走啊。 出了奈良站我们就去了一个无人值守的自行车出租店,拍一下护照,支付了现金就可以拿车了,我们租了三辆电动助力的。 先去公园看鹿,到处是鹿,味道很大。 门口的柱子好粗,两个人都不一定合抱的上。 中国人多,外国人也多,还有台湾人,热闹。 这只鹿很聪明,跑到水池里,就它一个,很多人喂它,吃到饱。这几个小朋友都是国内的。 逛了大半天饿得要死,在下山的路上找了一家面馆,等了一会儿,服务员出来说,时间快打烊了,不再接待了。又找了一家,也是营业到两点,还好客人比较少,我们是最后一拨。 家庭式面馆,味道还可以,卫生就不用说,整个店里都非常干净。因为不认识菜单,我随便点了一个,面太清淡了,两口我就干完了。临走时,老板还用中文说再见。 在日本吃饭,你会发现店里特别安静,都是小声说话,包括我们这些外国人。 总结 京都是一个非常适合慢慢游玩的地方,可以来多次,吃住我觉得都很方便,坐地铁,近铁什么的我是搞不懂,所以我们一直都在市内坐的公交车,慢是慢了点,但好认地方啊。 我发现日本的公交车有一个很有意思的地方,它的地盘是可以下降的,我估计能降 20cm 左右。这样比较方便上下车,而且碰到坐轮椅的,司机还要下来帮忙,有点累哈。 日本的商场商店饭馆等地方到点就打烊下班了,不会因为说还有几桌客人可以再接待一下,我觉得这样的生活挺好的,国内就完全不可能。 我在京都没看到一个送外卖的,快递等,电动车也没见过,摩托车有,但很少,自行车倒多了点,但也没有共享单车。下班的时候,好多人都是背个包步行,可能离家近吧,真好。 虽然在日本待了不到五天,但路上边走边看手机的人我遇到的可能都不到五个,这就很神奇了,深圳满大街的都是低头族。不知道日本人刷手机都刷啥,Twitter,YouTube 吗?不抖音? 我还逛了两个卖电子产品的商场,人没有华强北多但也不少,价格比京东贵,所以啥也没买。我本来还想看看日本的钢笔能不能捡个便宜的,结果商场里卖的都好贵,最后在日本基本上是啥也没买。 在日本,外面基本上看不到垃圾桶,也很少有搞卫生的人,最多有的店里有让扔饮料瓶,所以我们每天只能把垃圾带回酒店,酒店的人来分类处理了。国内的城市卫生全靠海量的清洁工人,以后人会越来越少,但大家却没个好习惯,咋办? 还有一个疑惑,京都我也逛了不少地方,就没看到一个卖片的店啊,更别说风月场所了。 这次我护照签的是 3 年多次,花了五百多,估计小棉袄初中毕业了还得再去了。日本也确实值得多次游玩,只不过语言不方便,我打算学习一年日语,看看能不能日常交流。 其实我还是喜欢去台湾,风景宜人,吃住行都非常舒适,价格和深圳差不多,人也特别友好,当然,已经七八年了,不知道现在如何了。比如香港,这十几年变化天翻地覆。

2025/9/6
articleCard.readMore

OpenWrt插件-应用过滤OpenAppFilter如何自定义特征库

之前我对 OpenWrt 的网络控制插件试用总结,最后只留下了插件 应用过滤(OpenAppFilter)。 应用过滤(OpenAppFilter)的特征库虽然开发者有 VIP 服务,提供了更丰富的特征库,但我的需求只针对个别的一些网站过滤,所以研究了一下如何自己添加特征码。 对特征库有刚需的可以购买 VIP 支持一下开发者,也不算贵 39。 特征库文件 首先在这里下载免费的特征库: 网址:https://www.openappfilter.com/#/feature 解压后有三个文件,两个 bin 文件是特征库,可以看升级说明两个文件的不同: 继续解压 bin 文件,可以看到一个 feature.cfg,一个 app_icons,很好理解了。 特征码格式 特征库文件用于存储所有 APP 的特征码,为非加密的文本文件,一般 .cfg 格式,可以通过文本文档编辑。 特征库文件说明:https://github.com/destan19/OpenAppFilter/wiki/self%E2%80%90define-feature-file 特征码用于定义每个 APP 的协议特征,比如可以定义端口号、域名、七层内容字典等: $id $name:[$proto;$sport;$dport;$host url;$request;$dict] $id: 唯一的 APP 编号,不能重复 $name: APP 的名称,比如抖音 $proto: 传输层协议,tcp 或 udp $sport: 源端口,1-65535,不设置表示匹配所有,一般不用设置源端口 $dport: 目的端口,1-65535,不设置表示匹配所有 $host: http或者https请求的域名,比如 www.baidu.com $dict: 七层内容字典,格式 xx:aa|yy:bb,其中 xx,yy 是位置,表示第几个字节,而 aa,bb 表示十六进制内容,示例:00:a0|02:08|03:0a,表示第 0 个位置为 0xa0,第 2 个位置为 0x08,第 3 个位置为 0x0a,注意第一位从 0 开始,如果位置为负数,表示从最后一个开始,一般不要用负数表示。 自定义特征库 打开特征库文件 feature.cfg 后可以看到如下内容: 1 2 3 4 5 6 7 #class video 3 视频 3001 抖音:[tcp;;;-dy-;;,tcp;;;-dy.;;,tcp;;;douyin;;,tcp;;;amemv.com;;,tcp;;;pstatp.com;;,tcp;;;volcsirius.com;;,tcp;;80;;^/pull.*.douyincdn.com;,tcp;;;ecombdapi.com;;,udp;;443;;;09:51|10:30|11:34;amemv.com;0,udp;;16000;;;00:00|01:01,udp;;1000-2000;;;00:00|01:01] 3006 斗鱼:[tcp;;;douyu;;,tcp;;;douyu;;-2:2f|-1:00] 3004 爱奇艺:[tcp;;;iqiyi;;,tcp;;;qy.net;;,tcp;;;inter.71edge.com;;,tcp;;;;^/videos;,tcp;;80;;;00:51|01:48|02:54,tcp;;80;;;09:00|10:00|11:00] 3014 哔哩哔哩:[tcp;;;bilivideo;;,tcp;;;bilibili.com;;,tcp;;;data.bilibili.com;;,tcp;;;biliapi.net;;,tcp;;;;;00:47|05:75|06:70|07:67,tcp;;;;/bfs/emote/;,tcp;;;hdslb.com;;,tcp;;1000-10000;;/v1/resource;,tcp;;8082;;;;00:16|01:03|02:01] 3008 虎牙直播:[tcp;;;huya;;,udp;;;;;01:00|02:00|03:00|04:23,udp;;;;;01:00|02:00|03:00|04:24] 3010 小红书:[tcp;;;xiaohongshu;;,tcp;;;xhscdn;;] 可以在同一个分类中复制某个 APP 特征,追加到该分类的最后一行,这样用于保证 appid 中的分类字段是统一的 appid 的低 3 位表示分类中的编号,高位表示分类 id,比如 3001,其中 3 表示视频分类,而 001 为编号,表示抖音。 复制后首先需要修改 appid,保证组内唯一,最好是组内编号最大值加 1,比如当前最大值为 3012,那新的 appid 就设置成 3013,这样方便统一管理。 比如现在要增加一个 web 游戏网站 poki.com,那新增的特征码定义如下: 1 2117 宝玩:[tcp;;;*.poki-cdn.com;;,tcp;;;poki.com;;,tcp;;;t.poki.io;;] 多个网址之间可以用 , 分隔,相互之间是或的关系。 添加完成后,记得把文件页首的版本号更新一下。 获取网站的 logo 图片,大小格式为 50x50,PNG,如果不想找,随便用个替代图片也是可以的。图片按格式保存到 app_icons 文件夹内,名称按对应的 appid 命名。 我发现 app_icons 文件夹里的 appid 数量很多,所以 appid 最好先看下文件夹内的最大编号。 浏览器分析域名 在 PC 端使用 Chrome 开发者工具分析网站域名比较方便。 使用 Chrome 打开网站前,先按键盘:F12 再切换到顶部 Network(网络)标签页。 勾选上面的选项 Preserve log,以便页面跳转后保留请求记录。 点击左上角的“🛑”停止按钮或“⨯”图标清除之前的请求。 在浏览器地址栏输入 https://poki.com 并回车,开始加载页面。 在 Network 里,有许多条请求记录,在 Name / Domain 列查看以下域名(如果没有就右键列首,勾选网域): poki.com *.poki.io *.poki-cdn.com 这些都是页面加载过程中涉及的域名,可以按规则加入 feature.cfg。 Wireshark 抓取没有域名的应用 有些没有明显域名但有独特流量特征的应用(如手游、专有加密协议)需要 Wireshark 抓包,获取 $dict 所需的字节特征(也就是七层协议数据中的特定字节内容)。 两种方法: 用笔记本做热点,手机 WiFi 连接笔记本,直接用 Wireshark 抓包分析 在路由器上用 tcpdump 抓取网络数据包,保存下来用 Wireshark 分析 .pcap 文件 我编译的 X86 固件添加了 tcpdump 插件,直接抓取,下载 pcap 文件,然后用 Wireshark 分析。 选择要抓取的端口; 设置抓取时间; 抓包; 等待完成后即可看到 .pcap 文件。但是在插件页面不知道为什么点按钮无法下载,更换浏览器也不行,只能通过文件管理去下载,目录:/tmp/tcpdump/cap/ 当然也可以在 OpenWrt 命令行中安装 tcpdump: 1 2 opkg update opkg install tcpdump 安装后通过终端命令抓包: 可以参考这里:OpenWrt抓包 Wireshark 打开文件后可使用下列过滤条件,如专门分析 IP:192.168.1.101 的设备数据: 1 ip.addr == 192.168.1.101 && (http.host || tls.handshake.extensions_server_name) 在任意一个 HTTPS 握手包选择后展开 Transport Layer Security → Extension: server_name,右键 → Apply as Column,主界面就会多一列 Server Name,这样所有请求对应的域名一眼就能看出来了。 Wireshark 界面如下: 选中该应用 TCP/UDP 会话,查看数据包内容。如下: 1 2 0000 a0 45 13 10 00 1c 00 00 00 03 01 0a 00 00 00 01 0010 4f 12 09 45 01 5d ... 对应 $dict 规则: 1 00:a0|01:45|02:13|03:10 规则匹配建议: 位置尽量靠前(前几十字节) 尽量使用固定字段(如协议标志) 避免使用 IP /端口变化的数据或会话 ID(不稳定) 可以通过比较多个数据包找出固定不变的字节序列 对于加密通信(如 HTTPS),可从 Client Hello / SNI 获取字节。 对于 WebSocket,可查看握手包中独特字段。 其实我对 Wireshark 抓包分析也不熟,都是现学现卖。 封装 bin 文件 feature3.0_cn_20250316.bin 文件虽然解压就可以自定义特征码了,但如何重新封装成 bin 文件呢?开始我以为直接压缩后将格式改成 bin 就完事了,后来发现根本不行。 于是我先分析了一下 feature3.0_cn_20250316.bin: 1 2 sudo apt install binwalk # Ubuntu/Debian 安装 binwalk binwalk feature3.0_cn_20250316.bin 接着会看到类似这样的输出: 1 2 3 DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 gzip compressed data, from Unix, last modified: 1970-01-01 00:00:00 (null date) 这说明 feature3.0_cn_20250316.bin 文件是纯 gzip 压缩文件。 目录结构如下: 1 2 3 4 feature/ ├── feature.cfg └── app_icons/ ├── ... 先进入 feature 文件夹目录,再打包,封装成 .bin 格式: 1 2 tar -cf feature.tar feature.cfg app_icons # 先将文件一起生成 tar 包 gzip -c feature.tar > feature3.0_cn_20250822.bin # 压缩成 .bin 格式 这样就生成了一个新的特征库文件:feature3.0_cn_20250822.bin。直接在插件管理页面上传就可以了。 应用过滤 特征库下载 分享一下我的特征库,添加了宝玩游戏和几个网盘。 夸克网盘分享:https://pan.quark.cn/s/e9e26f3f5881 提取码:n1MD 百度网盘分享:https://pan.baidu.com/s/1qcsD1XVwoLjC4J3vAwBh2A?pwd=hm8g 提取码: hm8g OpenWrt X86 固件 我编译的 X86 固件,有应用过滤和网络控制插件,当然还有各种科学上网工具: 夸克网盘分享:https://pan.quark.cn/s/275136450f54 百度网盘分享:https://pan.baidu.com/s/1BTbtZJQvvUXtfwCAknNJdQ?pwd=xg1r 提取码: xg1r 应用过滤 openappfilter 的问题 过滤的网址,有可能还会引起其他的网址无法访问,比如我屏蔽了几个国内视频网站,接着 Google 就无法搜索了。原因不明,我也没找到解决方法。(有时又正常了) 应用过滤这个插件会影像 Docker,如果应用过滤启动了,那么容器会报 500 错误,只能先启动容器,再开启应用过滤插件。 如果同时打开了科学上网插件,那么应用过滤的网站同时也是代理的网站,那过滤无效。比如我开启了所有国外网站都走科学上网,那么应用过滤里的所有国外网站还是都能正常访问的。这就是我为什么没有把国外的社交网站,视频网站加到特征库的原因。

2025/8/27
articleCard.readMore

Arduino烧录hex固件的多种方法

Arduino MCU HEX 文件下载器(烧录软件)我搜了一下,还是有不少的,但很多只支持原生 Arduino 开发板,对于 STM32,ESP32,ESP8266 很少有支持的,目前我找到的仅有 Freematics Builder 支持 ESP32。 ZTFlashWriter ZTFlashWriter 是哲涛工程师基于 Arduino 1.8.9 软件开发的离线 Arduino HEX 文件下载工具。 本工具不需要 Arduino IDE,即可直接把 HEX 文件下载到 Arduino 相关主板上。 本软件支持批量烧录,可以同时给连接在一台主机上的15个主板烧录同样的固件(HEX文件)。 本软件支持常见的所有 Arduino 原生开发板。 本软件不需要安装, 下载后解压即可打开使用。 本软件仅适用Windows系统,建议在Windows10+中使用。 下载链接: https://www.zhetao.com/mcu-download-tools.html OpenJumper™ Serial Assistant 同样的一款 Windows 下的 HEX 下载工具,看文件目录,下载应该是基于 avrdude,仅支持原生的 Arduino 开发板。而且该软件不开源,还停留在十几年前,适合老电脑使用。 下载链接: OpenJumper™ Serial Assistant 1.3.6beta.zip 链接:https://pan.quark.cn/s/b056c8c8bff6 提取码:QsLB 软件介绍: https://openjumper.com/doc/install https://www.cnblogs.com/wind-under-the-wing/p/14686625.html Freematics Builder 这是一个卖 OBD 公司开发的,主要是针对自己的产品,所以只有几种型号的 Arduino 支持,但是可以下载到 ESP32。 下载链接: FreematicsBuilder-1.3.1-win32.zip 链接:https://pan.quark.cn/s/9b9790da046a 提取码:7Rxy 有在 GitHub 提供下载,但似乎没有开放源码:https://github.com/stanleyhuangyc/Freematics/releases 使用介绍:https://blog.csdn.net/LostSpeed/article/details/129730169 avrdude 这个是命令行下载工具,支持 Windows,Linus,Mac,也仅支持 Arduino 原生开发板。 开发者源码: https://github.com/avrdudes/avrdude 使用介绍: https://hackmd.io/@Jub9Bu1BR0qRYeSAA0wUJQ/SkvOGZIE6 Arduloader 也是一款 Windows 下的工具,十年前的软件,仅支持 Arduino 原生开发板。 开发者源码: https://github.com/uname/Arduloader 使用介绍: https://uname.github.io/2015/07/15/arduloader/

2025/8/9
articleCard.readMore

2025最新-Google AdSense税务居住地证明如何申请?中国税收居民身份证明轻松搞定

Google AdSense 后台一直在提醒:“请采取行动:您的AdSense 收款账号中有一些需要核实的信息”。 我搜了一下,发现 Google AdSense 的税务居住地证明就是申请中国税收居民身份证明,看了几个教程都很麻烦,提供很多资料,甚至有的要去税务局提交资料。我的账号反正也没钱,就一直没管,后来看到国家税务局通告说 4 月 1 日起就可以在自然人电子税务局网上申请了。 五月份的时候,又搜索了一下,看到一些新的教程出来了,还要提供不少资料,而且还有和税务局的人沟通。 等到 7 月底,我试了一下,流程应该已经正常了,材料非常简单,已经无须人工沟通了。 首先访问 自然人电子税务局:https://etax.chinatax.gov.cn,登录后选择中国税收居民身份证明开具: 申请开具: 申请年度和国家,一般都是新加坡: 下一步,进入填报: 受雇单位都是默认无法更改的,如果是无业的话那我也不知道是啥了: 特殊要求:否 享受协定待遇 一般都是 Google 新加坡公司的名字: Google Asia Pacific Pte. Ltd. 协定名称:中华人民共和国政府和新加坡共和国政府关于对所得避免双重征税和防止偷漏税的协定 协定条款:独立个人劳务条款 协定待遇收入金额按人民币,起付标准是 100 美元,我也就这么多算不错了,所以 720 和减免 720×20%=144 附件上传 Google AdSense 服务条款,中英文版 PDF Google AdSense Online Terms of Service.pdf 链接:https://pan.quark.cn/s/71da29b4ccbe 提取码:czDE Google AdSense 在线服务条款中文版.pdf 链接:https://pan.quark.cn/s/b03713dba10b 提取码:ZUH3 上传身份证和户口簿,其中证明申请人英文名称的和身份证件都是上传的身份证,户口簿我上传了首页和个人页。 纳税人识别号即身份证号,所有文件我都是扫描成 PDF 格式: 点下一步,检查一下提交: 过了一周多,我忽然想起来了,登录查看,已经确认了: 下载 PDF: 登录 Google AdSense,删除之前提交的记录,重新上传,很快就审核通过了:

2025/8/7
articleCard.readMore

乌龟自动喂食器2.0升级

简介 去年五一之前给三只小龟做了个自动喂食器:赶在五一之前给乌龟做个自动喂食器,刚好一年过去了(我这拖延症),升级到了 2.0 版本,主要是增加了自动水循环系统。 结构设计 在结构上增加了一个循环水箱,一个过滤水箱。水从循环水箱里抽出来,经过过滤水箱后给乌龟们洗澡喝水,再回流到了循环水箱里面。画了一个示意图: 在经过一段时间的循环后,水必然会减少,除了乌龟们用的,还有蒸发的,所以还得往循环水箱里面加水。既然自动化了,当然不能让我每天盯着水少了就加啊。于是我就增加了一个检测水位的装置: 检测到了低水位后,水泵就打开,从水桶里往循环水箱里加水,一直加到高水位后水泵就停止了。 你问桶里没水了怎么办?当然是我手动加了,我可舍不得钱买一个能电动控制的水龙头,一个星期加一桶水,顺便清晰一下几个水箱,更换一下过滤棉。 硬件设计 主控还是 ESP8266 模块,除了自动喂食器的电机驱动,增加了两个水泵的电源控制继电器,再用两个 IO 来检测水位。 一个水泵是循环抽水的,可以通过云端设置开关的时间间隔,另一个水泵是加水的,当检测到低水位时,该水泵就打开,水位到最高点时自动关闭水泵。 监测水位的我用的是干簧管和磁铁,在防水上没什么问题,也不用多余的工作。 中间的白色浮子里面嵌入了一个小磁铁,可以飘浮在水中,随着水位升降。当水面下降时,磁铁会靠近底下的干簧管,干簧管短路,IO 电平由低变高。水位上升的时候磁铁会离开底下的干簧管,干簧管开路,IO 由高变低,当靠近上面的干簧管后,IO由低变高,说明水面到了高水位。 开始本来的想法是用水的导电性来设计水位传感器的,淘宝上卖的也基本都是这类,因为水箱离主控板比较远,线缆一长,ADC 转换的时候容易出错,所以我就改成了用 IO 电平变化来监测水位了。 电路很简单,也没考虑很多,能用就行,仅供 DIY 用。 软件设计 软件设计还是 ESP8266 模块作为 Arduino 板联网控制,通过 Arduino IoT Cloud 连接设备。 Arduino Cloud IoT 使用入门指南 Arduino 代码很简单,注释我也写得很详细,流程图如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 /* Sketch generated by the Arduino IoT Cloud Thing "Autofeed2.0" https://create.arduino.cc/cloud/things/a4043153-6bfb-4dfa-a055-151cba4627a4 Arduino IoT Cloud Variables description The following variables are automatically generated and updated when changes are made to the Thing CloudCounter motorCloudCycleHours; bool motorCloudControl; bool pumpCloudControl; CloudTime pumpCloudOffSecond; CloudTime pumpCloudOnSecond; Variables which are marked as READ/WRITE in the Cloud Thing will also have functions which are called when their values are changed from the Dashboard. These functions are generated with the Thing and added at the end of this sketch. */ #include "thingProperties.h" // 软件版本号 const char* softwareVersion = "2.1"; // 定义引脚 const int pwm1Pin = 12; // GPIO12 (D6) const int pwm2Pin = 14; // GPIO14 (D7) const int pumpPin = 13; // GPIO13 (D5) const int pumpINPin = 16; // GPIO13 (D5) const int pumpLowPin = 5; // GPIO4 (D2) const int pumpHighPin = 4; // GPIO5 (D1) // 系统故障状态 bool systemFault = false; //进水泵状态变量 bool pumpINRunning = false; unsigned long pumpINStartTime = 0; // 记录水泵开启的时间戳 const unsigned long PUMP_TIMEOUT_MS = 2 * 60 * 1000; // 进水泵运行超时2分钟 // --- 用于非阻塞去抖动的变量 --- unsigned long lastLowPinDebounceTime = 0; // 上次低水位引脚状态变化的时间(毫秒) unsigned long lastHighPinDebounceTime = 0; // 上次高水位引脚状态变化的时间(毫秒) const long DEBOUNCE_DELAY_MS = 300; // 去抖动延迟时间 (毫秒) int debouncedLowPinState = LOW; // 经过去抖动后的低水位引脚状态 int debouncedHighPinState = LOW; // 经过去抖动后的高水位引脚状态 int lastLowReading = -1; int lastHighReading = -1; // --- 通用的去抖动函数 --- // pin: 要读取的引脚 // lastStableState: 存储该引脚上次稳定的状态 (需要传入引用&) // lastDebounceTime: 存储该引脚上次去抖动的时间 (需要传入引用&) // lastReading: 存储该引脚上次读取的状态 // 返回值: 经过去抖动后的稳定状态 int debounceRead(int pin, int& lastStableState, unsigned long& lastDebounceTime,int& lastReading) { int reading = digitalRead(pin); // 读取引脚当前原始状态 // 只有当原始读取值与去抖动后的稳定状态不同时,才重置去抖动计时器 if (reading != lastReading) { lastDebounceTime = millis(); } // 如果从上次潜在状态变化到现在的时间超过了去抖动延迟 if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY_MS) { if (reading != lastStableState){ lastStableState = reading; } } lastReading = reading; return lastStableState; // 返回去抖动后的稳定状态 } // 喂食电机状态变量 bool motorRunning = false; unsigned long motorStartTime = 0; // 记录电机本次启动的时间 const unsigned long motorRunDurationMillis = 25 * 1000; // 电机每次运行时间 秒 // 电机自动循环定时变量 unsigned long motorCycleStartTime = 0; // 记录上次自动循环启动的时间 unsigned long motorCycleDurationMillis = 0; // 自动循环间隔(从Cloud获取时间为小时,转换为毫秒) // 循环水泵状态变量 bool pumpRunning = false; // 控制水泵的总开关(受Cloud和遥控按键影响) bool pumpOnPeriod = false; // true表示处于水泵的ON阶段,false表示处于OFF阶段 unsigned long lastPumpToggleTime = 0; // 记录上次水泵ON/OFF切换的时间 unsigned long pumpOnDurationMillis = 0; // 水泵ON时长(从Cloud获取时间为秒,转换为毫秒) unsigned long pumpOffDurationMillis = 0; // 水泵OFF时长(从Cloud获取时间为秒,转换为毫秒) // --- 函数原型声明 --- void startMotor(); void stopMotor(); void setPumpState(bool state); void stopPump(); void setup() { // Initialize serial and wait for port to open: Serial.begin(115200); // This delay gives the chance to wait for a Serial Monitor without blocking if none is found Serial.println("Software Version: " + String(softwareVersion)); delay(100); // Defined in thingProperties.h initProperties(); // Connect to Arduino IoT Cloud ArduinoCloud.begin(ArduinoIoTPreferredConnection); // 设置引脚模式 pinMode(pwm1Pin, OUTPUT); pinMode(pwm2Pin, OUTPUT); pinMode(pumpPin, OUTPUT); pinMode(pumpINPin, OUTPUT); pinMode(pumpHighPin, INPUT); pinMode(pumpLowPin, INPUT); // 初始化输出状态 analogWrite(pwm1Pin, 0); analogWrite(pwm2Pin, 0); digitalWrite(pumpPin, LOW); digitalWrite(pumpINPin, LOW); // 初始化定时器(确保Cloud属性获取到初始值后才计算) // 第一次启动时,假定自动循环从现在开始计时 motorCycleStartTime = millis(); //电机循环开始计时 lastPumpToggleTime = millis(); //水泵的循环也从现在开始计时(如果Cloud开关打开) Serial.println("Arduino Cloud Connected!"); /* The following function allows you to obtain more information related to the state of network and IoT Cloud connection and errors the higher number the more granular information you’ll get. The default is 0 (only errors). Maximum is 4 */ setDebugMessageLevel(2); ArduinoCloud.printDebugInfo(); } void loop() { ArduinoCloud.update(); //--- 处理进水检测和水泵开关逻辑 --- // 读取水位状态并进行去抖动处理 (非阻塞方式) debouncedLowPinState = debounceRead(pumpLowPin, debouncedLowPinState, lastLowPinDebounceTime, lastLowReading); debouncedHighPinState = debounceRead(pumpHighPin, debouncedHighPinState, lastHighPinDebounceTime, lastHighReading); // 打印当前所有相关变量的实时值 Serial.printf("实时状态 -> 低水位: %d, 高水位: %d, 水泵运行中: %d\n", debouncedLowPinState, debouncedHighPinState, pumpINRunning); // 当高位检测到高电平并且进水泵在运行状态,水泵马上停止 if ((debouncedHighPinState == HIGH && debouncedLowPinState == LOW) && pumpINRunning == true) { digitalWrite(pumpINPin, LOW); Serial.printf("水泵状态: 关闭\n"); // 设置电机状态为停止 pumpINRunning = false; } // 当低水位检测到高电平并且进水泵没有运行没有故障,则马上抽水 if ((debouncedLowPinState == HIGH && debouncedHighPinState == LOW) && pumpINRunning == false && !systemFault) { digitalWrite(pumpINPin, HIGH); Serial.printf("水泵状态: 开启\n"); pumpINStartTime = millis(); // 设置电机状态为运行 pumpINRunning = true; } // --- 进水泵超时保护 --- if (pumpINRunning == true) { if (millis() - pumpINStartTime >= PUMP_TIMEOUT_MS) { digitalWrite(pumpINPin, LOW); Serial.printf("进水泵状态: 关闭 (超时 - 系统可能故障!)\n"); pumpINRunning = false; systemFault = true; // 设置系统故障标志 } } // --- 处理喂食电机逻辑 --- // 自动循环定时启动 // 检查Cloud开关是否打开 AND 电机当前未运行 AND 距离上次自动启动已过去设定时间 motorCycleDurationMillis = (unsigned long)motorCloudCycleHours * 3600UL * 1000UL; // 实时更新循环周期,cloud 计数为小时 if (motorCloudControl && !motorRunning && motorCycleDurationMillis > 0 && (millis() - motorCycleStartTime >= motorCycleDurationMillis)) { Serial.println("Motor triggered by automatic cycle."); startMotor(); motorStartTime = millis(); // 记录本次启动时间 motorRunning = true; motorCycleStartTime = millis(); // 重置自动循环计时 } // 电机运行时间到期停止 if (motorRunning && (millis() - motorStartTime >= motorRunDurationMillis)) { Serial.println("Motor run duration finished."); stopMotor(); motorRunning = false; // 如果是通过Cloud开关启动的,当运行时间到期时,Cloud开关状态保持ON // 如果是通过自动循环或外部触发启动的,运行时间到期停止,Cloud开关状态不变 } // --- 处理循环水泵逻辑 --- // 只有当Cloud控制开启并且无故障时,水泵才进入循环模式 if (pumpCloudControl && !systemFault) { // 实时更新泵的ON/OFF时长 pumpOnDurationMillis = (unsigned long)pumpCloudOnSecond * 1000UL; // 从Cloud获取并转换为毫秒 pumpOffDurationMillis = (unsigned long)pumpCloudOffSecond * 1000UL; // 从Cloud获取并转换为毫秒 // 检查当前处于ON还是OFF周期,并判断是否需要切换 if (pumpOnPeriod) { // 当前是ON周期 if (millis() - lastPumpToggleTime >= pumpOnDurationMillis && pumpOnDurationMillis > 0) { Serial.println("Pump ON period finished, switching to OFF."); pumpOnPeriod = false; // 切换到OFF周期 lastPumpToggleTime = millis(); // 记录切换时间 setPumpState(pumpOnPeriod); // 更新水泵引脚状态 (LOW) } } else { // 当前是OFF周期 if (millis() - lastPumpToggleTime >= pumpOffDurationMillis && pumpOffDurationMillis > 0) { Serial.println("Pump OFF period finished, switching to ON."); pumpOnPeriod = true; // 切换到ON周期 lastPumpToggleTime = millis(); // 记录切换时间 setPumpState(pumpOnPeriod); // 更新水泵引脚状态 (HIGH) } } pumpRunning = true; // 只要Cloud开关打开,即使在OFF周期,逻辑上水泵是处于“运行中”循环模式 setPumpState(pumpOnPeriod); // 确保引脚状态与当前周期一致 } else { // 如果Cloud开关关闭,停止水泵循环并关闭水泵 if(pumpRunning) { // 避免重复执行关闭操作 Serial.println("Pump Cloud control turned OFF, stopping pump cycle."); stopPump(); pumpRunning = false; pumpOnPeriod = false; // 确保回到初始状态 } } delay(50); } void startMotor() { digitalWrite(pwm1Pin, HIGH); //逆时针旋转 digitalWrite(pwm2Pin, LOW); Serial.println("Motor started."); } void stopMotor() { digitalWrite(pwm1Pin, LOW); digitalWrite(pwm2Pin, LOW); // 停止电机 Serial.println("Motor stopped."); } void setPumpState(bool state) { digitalWrite(pumpPin, state ? HIGH : LOW); } void stopPump() { setPumpState(false); // 直接关闭水泵 } /* Since PumpCloudControl is READ_WRITE variable, onPumpCloudControlChange() is executed every time a new value is received from IoT Cloud. */ void onPumpCloudControlChange() { Serial.printf("pumpCloudControl changed to: %s\n", pumpCloudControl ? "true" : "false"); if (pumpCloudControl) { // 从Cloud开启水泵,立即开始循环 Serial.println("Starting pump cycle from Cloud."); pumpRunning = true; pumpOnPeriod = true; lastPumpToggleTime = millis(); // 重置泵循环计时 setPumpState(pumpOnPeriod); // 立即打开水泵 } else { // 从Cloud关闭水泵,立即停止水泵和循环 Serial.println("Stopping pump cycle from Cloud."); stopPump(); pumpRunning = false; pumpOnPeriod = false; // 确保回到初始状态 } } /* Since MotorCloudControl is READ_WRITE variable, onMotorCloudControlChange() is executed every time a new value is received from IoT Cloud. */ void onMotorCloudControlChange() { Serial.printf("motorCloudControl changed to: %s\n", motorCloudControl ? "true" : "false"); if (motorCloudControl) { // 从Cloud开启电机,运行25秒 startMotor(); motorStartTime = millis(); motorRunning = true; } else { // 从Cloud关闭电机,立即停止 stopMotor(); motorRunning = false; } } /* Since MotorCloudCycleHours is READ_WRITE variable, onMotorCloudCycleHoursChange() is executed every time a new value is received from IoT Cloud. */ void onMotorCloudCycleHoursChange() { motorCycleStartTime = millis(); } /* Since PumpCloudOffSecond is READ_WRITE variable, onPumpCloudOffSecondChange() is executed every time a new value is received from IoT Cloud. */ void onPumpCloudOffSecondChange() { // Add your code here to act upon PumpCloudOffSecond change } /* Since PumpCloudOnSecond is READ_WRITE variable, onPumpCloudOnSecondChange() is executed every time a new value is received from IoT Cloud. */ void onPumpCloudOnSecondChange() { // Add your code here to act upon PumpCloudOnSecond change } 可以看到上面的代码是 V2.1 版本,因为 V2.0 开始用的第一天,从乌龟槽流到循环水箱的下水管堵住了,导致水没法循环了,所以循环水箱里的水一直减少,加水的泵一直就加,一满桶水很快没了。我赶紧关闭了电源进行清理。 结果没过两天,早上五点多被嗡嗡声吵醒了,我一看,循环水箱没水了,水桶也没水了,导致加水泵一直开着,还不知道干烧了多久。我还在想昨晚桶里水不少,为什么这样,仔细一看,水桶底漏了一个洞。靠,刚烧了固件就连续出现两个意外,促使我完善了代码。 于是我就增加了加水泵保护的代码,持续工作时长超过两分钟自动停止,并报故障,直到系统重启。 下载 Arduino APP,登录,可以看到设备上线了,点击按键控制,因为 Arduino IoT Cloud 对免费版的 Cloud Variables 总数量有限制,所以开关项就这么几个了。 以上所有原始文件在此,仅供参考,有问题自己搜索或问 AI: https://github.com/harry10086/Autofeed

2025/7/24
articleCard.readMore

如何迁移群晖套件到不同的存储空间

前言 群晖之前的套件都安装在一个小硬盘,最近换了个大硬盘,文件可以直接在共享文件夹里修改存储空间就好了。 群晖 DSM 虽然有各种备份,迁移,但套件是没有。怎么才能迁移到新的硬盘,我不想重新安装软件了。 搜了一下,发现有个迁移自动化脚本,使用起来还可以。 下载 项目地址:https://github.com/007revad/Synology_app_mover 实际有用的只有 syno_app_mover.sh 和 syno_app_mover.conf,下载这两个文件就可以了,一个是脚本,一个是配置。 现有备份和恢复模式。 支持 DSM 7。未使用 DSM 6 进行全面测试。 如果备份到 USB 驱动器,分区的文件系统应该是 ext3,ext4,btrfs。 迁移 将下载的两个文件放到群晖的任意文件夹内,syno_app_mover.conf 主要是备份相关的,比如备份目录,剩余空间限制等,我是直接迁移。 启用群晖 SSH,并使用 Xshell 或 PUTTY 连接到 NAS 登录。 1 2 3 4 5 6 # 切换到 root 权限 sudo -i # 进入syno_app_mover.sh所在目录 cd /volume1/scripts # 运行脚本 ./syno_app_mover.sh 选择 1 move,可以看到所有套件和所在分区,按序号选择后迁移到其他盘,按照步骤很容易。 就是有一点不方便,套件得一个一个迁移,没法批量。

2025/7/16
articleCard.readMore