<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>TLog</title><link href="https://techie-s.work/" rel="alternate"/><link href="https://techie-s.work/feeds/all.atom.xml" rel="self"/><id>https://techie-s.work/</id><updated>2026-02-05T20:10:11+08:00</updated><entry><title>Android折腾重启报set_policy_failed:/data/adb</title><link href="https://techie-s.work/posts/2026/02/fix-set_policy_failed-when-tweaking-ksu/" rel="alternate"/><published>2026-02-05T20:10:11+08:00</published><updated>2026-02-05T20:10:11+08:00</updated><author><name/></author><id>tag:techie-s.work,2026-02-05:/posts/2026/02/fix-set_policy_failed-when-tweaking-ksu/</id><summary type="html">&lt;p&gt;TL;DR: 修改init脚本，把 &lt;code&gt;init.usb.rc&lt;/code&gt; 中 &lt;code&gt;encryption=Require&lt;/code&gt; 改成&lt;code&gt;encryption=DeleteIfNecessary&lt;/code&gt;，在system分区中应用修改，再重启。&lt;/p&gt;&lt;p&gt;调试 KernelSU，把 &lt;code&gt;/data/adb&lt;/code&gt; 删了，manager创建了个新的。结果就这样。&lt;/p&gt;</summary><content type="html">
&lt;p&gt;TL;DR: 修改init脚本，把 &lt;code&gt;init.usb.rc&lt;/code&gt; 中 &lt;code&gt;encryption=Require&lt;/code&gt; 改成&lt;code&gt;encryption=DeleteIfNecessary&lt;/code&gt;，在system分区中应用修改，再重启。&lt;/p&gt;
&lt;h2 id="起因"&gt;起因&lt;a class="headerlink" href="#起因" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;调试 KernelSU，把 &lt;code&gt;/data/adb&lt;/code&gt; 删了，manager创建了个新的。结果就这样。&lt;/p&gt;
&lt;h2 id="原理"&gt;原理&lt;a class="headerlink" href="#原理" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;File based encryption. init脚本会在开机时配置/创建所需要的文件目录结构，但若所设加密策略与实际不匹配，就会报错要求擦除数据。&lt;/p&gt;
&lt;p&gt;以LineageOS 23.0为例，&lt;/p&gt;
&lt;p&gt;见 &lt;code&gt;system/core/init/builtins.cpp&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IsFbeEnabled&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;FscryptSetDirectoryPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref_basename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fscrypt_action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reboot_into_recovery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"--prompt_and_wipe_data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"--reason=set_policy_failed:"&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;又见 &lt;code&gt;system/core/init/fscrypt_init_extensions.cpp&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;FscryptSetDirectoryPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ref_basename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FscryptAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                               &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FscryptAction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;kNone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SetPolicyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref_basename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FscryptAction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;kAttempt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FscryptAction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;kDeleteIfNecessary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Setting policy failed, deleting: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;delete_dir_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SetPolicyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref_basename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;而 &lt;code&gt;init.usb.rc&lt;/code&gt; 中有：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;on post-fs-data
    ...(skip)
    mkdir /data/adb 0700 root root encryption=Require
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;导致 &lt;code&gt;/data/adb&lt;/code&gt; 出加密配置问题时系统会提示擦除数据。&lt;/p&gt;
&lt;p&gt;幸好有办法在遇到错误时删掉整个目录……不然从备份恢复也够麻烦的……&lt;/p&gt;</content><category term="Misc"/><category term="android"/></entry><entry><title>为我的监控摄像头适配Thingino</title><link href="https://techie-s.work/posts/2026/01/port-thingino-to-my-webcam/" rel="alternate"/><published>2026-01-20T15:47:26+08:00</published><updated>2026-01-20T15:47:26+08:00</updated><author><name/></author><id>tag:techie-s.work,2026-01-20:/posts/2026/01/port-thingino-to-my-webcam/</id><summary type="html">&lt;p&gt;因为一些原因，得到了一部“双目云台摄像头”。因为讨厌原系统依赖的云平台和私有软件，故尝试刷机/修改系统。这篇文章是 …&lt;/p&gt;</summary><content type="html">
&lt;h2 id="契机"&gt;契机&lt;a class="headerlink" href="#契机" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;因为一些原因，得到了一部“双目云台摄像头”。因为讨厌原系统依赖的云平台和私有软件，故尝试刷机/修改系统。这篇文章是根据进度日报对整个过程的复盘。&lt;/p&gt;
&lt;h2 id="设备分析"&gt;设备分析&lt;a class="headerlink" href="#设备分析" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;摄像头型号： LY-SM-YT40&lt;ul&gt;
&lt;li&gt;完全看不出名堂&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;厂商：合肥岭雁科技（linkintec）&lt;/li&gt;
&lt;li&gt;生产企业：深圳市乔安科技（Jooan）&lt;/li&gt;
&lt;li&gt;云平台：移动爱家&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="拆解过程概要"&gt;拆解过程概要&lt;a class="headerlink" href="#拆解过程概要" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;按压底部二维码的贴纸，发现两处可能的螺丝位，撕开（表扬一下，贴纸质量不错）符合预期。卸下螺丝，便能卸下摄像头的下半部分，暴露出本体与下部副摄像头的连接排线。解开排线，彻底分离下半部分外壳。&lt;/p&gt;
&lt;p&gt;&lt;img alt="底部照片" src="https://techie-s.work/posts/2026/01/port-thingino-to-my-webcam/photos/1.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="下半部分内部" src="https://techie-s.work/posts/2026/01/port-thingino-to-my-webcam/photos/2.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;将云台轻轻旋转对齐至中位，发现四个螺丝孔位，推测它们固定了上半部分外壳，卸下后验证了想法。轻轻拆除上半部分外壳。&lt;/p&gt;
&lt;p&gt;摄像头电路主体在云台半球后方暴露出来。为保证稳定性，云台电机和电源部分点有红色胶水。暂时可以不拆线路。观察发现云台上半球是直接扣进转轴，轻掰转轴两侧支架、取下半球。&lt;/p&gt;
&lt;p&gt;&lt;img alt="半球拆除" src="https://techie-s.work/posts/2026/01/port-thingino-to-my-webcam/photos/3.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;卸下电机和散热硅胶块块，暴露出了SoC本体和NAND存储器，可以确认平台为Ingenic T40XP。&lt;/p&gt;
&lt;p&gt;&lt;img alt="本体暴露" src="https://techie-s.work/posts/2026/01/port-thingino-to-my-webcam/photos/4.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;进一步拆解发现，靠近中间的两颗螺丝是镜头模组固定螺丝，而周边三颗螺丝才是主板固定螺丝。&lt;/p&gt;
&lt;p&gt;&lt;img alt="主板拆除" src="https://techie-s.work/posts/2026/01/port-thingino-to-my-webcam/photos/5.jpg"/&gt;&lt;/p&gt;
&lt;h3 id="固件备份"&gt;固件备份&lt;a class="headerlink" href="#固件备份" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;首先尝试了从串口终端直接备份，但被u-boot和系统双重密码挡下。又尝试了官方推荐的ingenic-cloner，但似乎没有现成的配置，并且电脑USB不认这台设备。手上也没有合适的工装。只好选择拆芯片上编程器来备份固件。&lt;/p&gt;
&lt;p&gt;NAND型号为FORESEE F35SQA001G，容量128MiB，采用CH341A和 &lt;a href="https://github.com/McMCCRU/SNANDer"&gt;SNANDer&lt;/a&gt; 进行备份。备份速度挺慢的。
重复备份操作，得到两份备份，对比确认读取无误。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;snander&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;dump.bin
snander&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;dump2.bin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="固件拆解"&gt;固件拆解&lt;a class="headerlink" href="#固件拆解" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;根据u-boot启动日志可见分区表（也可以从dump里的u-boot env中找到），对比binwalk结果判断，可以按它分割固件。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Creating 8 MTD partitions on "sfc_nand":
0x000000000000-0x000000100000 : "boot"
0x000000100000-0x000000200000 : "bootenv"
0x000000200000-0x000000600000 : "kernel"
0x000000600000-0x000001200000 : "rootfs"
0x000001200000-0x000001a00000 : "appfs"
0x000001a00000-0x000004c00000 : "config"
0x000004c00000-0x000005400000 : "appfs1"
0x000005400000-0x000005500000 : "confbak"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;编写python脚本（也可以直接dd）处理固件，拆分各个分区。经检查，rootfs存放了系统通用基础组件，而appfs存放了streamer等服务的主程序。系统为busybox init。rootfs和appfs均为squashfs，可以通过squashfuse进行挂载。&lt;/p&gt;
&lt;p&gt;其它次要分区有UBI格式的，处理起来比较繁琐，但若只是提取文件，可以使用&lt;code&gt;ubi_reader&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;uvx&lt;span class="w"&gt; &lt;/span&gt;--from&lt;span class="w"&gt; &lt;/span&gt;ubi_reader&lt;span class="w"&gt; &lt;/span&gt;ubireader_extract_files&lt;span class="w"&gt; &lt;/span&gt;ubi_partition_image.bin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;从init脚本来看，rootfs在启动时仅仅是加载了WiFi驱动、设定一些性能参数，随后将控制权交给appfs的&lt;code&gt;jzstart.sh&lt;/code&gt;进一步加载特定sensor驱动、启动主程序。&lt;/p&gt;
&lt;p&gt;后续分析发现，部分设备GPIO配置明文存储在全局配置文件&lt;code&gt;config.org&lt;/code&gt;（json，后缀表示origin）中，推测是为了方便复用相关软件。&lt;/p&gt;
&lt;h3 id="修改固件获取shell"&gt;修改固件获取shell&lt;a class="headerlink" href="#修改固件获取shell" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;linux kernel的入口点由u-boot env给的cmdline确认，而它们恰巧又在bootenv分区，故我选择修改了bootenv分区的内容。编写了一个脚本，将txt格式的boot env转换成binary格式。头部四个字节是后续所有字节的CRC32值。&lt;/p&gt;
&lt;p&gt;将cmdline中&lt;code&gt;init=/linuxrc&lt;/code&gt;修改为&lt;code&gt;init=/bin/sh&lt;/code&gt;，得到了修改后的bootenv分区，一样通过snander刷入NAND芯片：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# NAND写入前要擦除，snander不会自动处理&lt;/span&gt;
snander&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;0x000000100000&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;0x000000100000
snander&lt;span class="w"&gt; &lt;/span&gt;-w&lt;span class="w"&gt; &lt;/span&gt;modded_env.bin&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;0x0000000000100000&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;0x0000000000100000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;虽然获取了shell，但没什么用处，只是导出了一份设备树。&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="初步适配thingino"&gt;初步适配Thingino&lt;a class="headerlink" href="#初步适配thingino" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="理解thingino"&gt;理解Thingino&lt;a class="headerlink" href="#理解thingino" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Thingino使用Buildroot2构建，对每个摄像头都有自己的配置。我在ArchLinux上创建了个Ubuntu 24.04的rootfs用于环境搭建。复制了一份摄像头配置，参考项目文档开始编译。而测试发现master分支难以编译，须选用stable分支进行适配。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GROUP&lt;/code&gt;环境变量可以用来选择摄像头配置文件夹。比如默认是&lt;code&gt;camera&lt;/code&gt;，但设置了GROUP之后，就会变成&lt;code&gt;camera-$GROUP&lt;/code&gt;，方便区分不同状况的摄像头配置。而&lt;code&gt;camera-exp&lt;/code&gt;就属于测试中的摄像头配置。&lt;/p&gt;
&lt;p&gt;有了在u-boot env和设备配置文件中找到的GPIO信息，初步配置得以完成。&lt;/p&gt;
&lt;h3 id="编译并安装"&gt;编译并安装&lt;a class="headerlink" href="#编译并安装" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;跳过编译过程。&lt;/p&gt;
&lt;p&gt;由于开源版u-boot暂时缺少对NAND flash的支持，我便将NAND换成了16MiB的NOR flash，反正都能用。 ¯\&lt;em&gt;(ツ)&lt;/em&gt;/¯&lt;/p&gt;
&lt;p&gt;但是启动不了……每次启动后，u-boot会在清除env分区时将整个flash清空……花了点时间定位问题，是flash擦除页面大小和分区没有对齐，根源在flash驱动内部状态overflow。修好擦除大小问题后，成功通过串口看到了Thingino后台（不放图了，按F键进入failsafe模式）。&lt;/p&gt;
&lt;h2 id="适配摄像头组件"&gt;适配摄像头组件&lt;a class="headerlink" href="#适配摄像头组件" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;到目前为止，设备能跑Thingino，但是摄像头、照明、联网功能全部失灵，需要适配/修复。&lt;/p&gt;
&lt;h3 id="按钮和指示灯"&gt;按钮和指示灯&lt;a class="headerlink" href="#按钮和指示灯" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id="按钮"&gt;按钮&lt;a class="headerlink" href="#按钮" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;按钮有两个，reset和call/通话。&lt;/p&gt;
&lt;p&gt;按钮也好找，因为按钮是输入量，直接所有的GPIO读一遍，按住按钮再读一遍，就能找到了。&lt;/p&gt;
&lt;p&gt;另外reset这个按钮有在配置里写出来。&lt;/p&gt;
&lt;h4 id="指示灯"&gt;指示灯&lt;a class="headerlink" href="#指示灯" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;这个难搞，纯猜的。配置里有写照明灯的GPIO，之前有测出按钮的GPIO，猜了个范围，试了下，没想到猜对了。&lt;/p&gt;
&lt;p&gt;不敢轻易将所有GPIO设置为输出试一遍就是怕不小心玩坏什么。&lt;/p&gt;
&lt;h3 id="wifi"&gt;WiFi&lt;a class="headerlink" href="#wifi" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;WiFi是ATBM6012BX，这点可以通过3点轻松判断：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;拆包检查既有驱动&lt;/li&gt;
&lt;li&gt;查看芯片丝印&lt;/li&gt;
&lt;li&gt;lsusb查看PID:VID&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;真没想到这里的WiFi竟然走USB。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;WiFi模块的电源有个开关，很巧的是这个GPIO写在了原厂固件配置里。轻松配置好。但是在系统启动后遇到了意料之外的错误：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mf"&gt;62.812011&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;atbm6012bx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Direct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;firmware&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;firmware&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;atbm6012bx_fw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不对呀，固件明明一起打包进去了，怎么会找不到？（&lt;code&gt;-2&lt;/code&gt;代表&lt;code&gt;-ENOENT&lt;/code&gt;，no such file or directory）没办法，clone一份kernel源码看看问题。找到&lt;code&gt;drivers/base/firmware_class.c&lt;/code&gt;中这样写：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* direct firmware loading support */&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fw_path_para&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fw_path&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;fw_path_para&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;"/lib/firmware/updates/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UTS_RELEASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;"/lib/firmware/updates"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;"/lib/firmware/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UTS_RELEASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;"/lib/firmware"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;原来固件默认加载位置就那么几个（&lt;code&gt;fw_path_para&lt;/code&gt;也只是多自定义一个）……而WiFi模块驱动里写的固件名称是个绝对路径，相当于路径重复了两遍……&lt;/p&gt;
&lt;p&gt;就先临时打个补丁改下吧。然后就好了，嗯。&lt;/p&gt;
&lt;h3 id="摄像头传感器sensor"&gt;摄像头传感器（sensor）&lt;a class="headerlink" href="#摄像头传感器sensor" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;研究一番发现摄像头传感器走的是MIPI CSI，在预期之中。一开始是一点头绪都没有，就尝试直接把原厂驱动插进去看看能不能用。有点曲折的是我还不怎么会用buildroot。总之想办法塞进去后，它能用。&lt;/p&gt;
&lt;p&gt;不过实际上是我先尝试逆向。使用Ghidra反编译原厂sensor驱动的内核模块后，提取sensor初始化参数，发现自己的逆向驱动有图像却很暗，非常不爽，感觉功亏一篑。折腾许久，甚至用Ghidra逆向了自己的内核模块，才发现程序流程出现了意外的分支，究其根源是switch-case中漏掉了一个break (XoX)&lt;/p&gt;
&lt;p&gt;中间本想通过加载已有源代码来加快分析，但是加载所需配置非常头疼，完全搞不懂，所以没有一个header加载成功过…… QvQ&lt;/p&gt;
&lt;p&gt;我的摄像头有两个sensor，也是逆向了才知道，主摄和副摄的数据线正好和SoC自己的顺序反过来。&lt;/p&gt;
&lt;p&gt;尝试了用一个内核驱动服务多个同款sensor，结果不行，似乎因为共享内存问题，内核会Oops。还是别Oops为好，不差这点体积。&lt;/p&gt;
&lt;p&gt;总之sensor驱动也就搞定了。然后我也成了Ghidra初级用户。&lt;/p&gt;
&lt;h3 id="云台电机"&gt;云台电机&lt;a class="headerlink" href="#云台电机" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;云台用的是5线四相步进电机。从原厂固件中的脚本分析，电机的引脚同样写在设备全局配置中。对比了下既有配置，竟然还找到一款类似产品，Jooan出品。不过还是得测试。&lt;/p&gt;
&lt;p&gt;SDK中4.4内核的电机版本与Thingino不兼容，我尝试把3.10的直接复制过去，发现用不了，怎么办呢？只好在结合4.4既有基础上从3.10的电机驱动程序那边引入对应的功能。&lt;/p&gt;
&lt;p&gt;结果改着改着，发现原来3.10的电机驱动程序里藏了几个核心bug，也难怪直接复制之后不能用。主要&lt;strong&gt;问题&lt;/strong&gt;包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把所有相位都通电当成了上电正常状态&lt;/li&gt;
&lt;li&gt;遗漏的逻辑反转判断&lt;/li&gt;
&lt;li&gt;在spinlock和interrupt上下文中使用不安全的&lt;code&gt;gpio_direction_output()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在4.4内核的既有代码算法有点意思，会计算最大公约数、将斜方向移动分成相同的小段，尝试解决运动抖动的问题。可惜光这样还不够，抖动会在每一个小段的末尾发生，因为移动距离较短的方向会不断加减速。我的解决方法是，在每个小段上，将运动较短方向上的触发时机均匀地分散开，与较长的对齐，尽量保证两边消耗的时间相同。算法细节就不提了，没什么含量（还花了我不少时间，脑子不行叻）。不过interrupt上下文内不能做除法，就因为除以零会触发中断，这点算是学到了。&lt;/p&gt;
&lt;h3 id="其它组件"&gt;其它组件&lt;a class="headerlink" href="#其它组件" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;运气比较好，什么IR-CUT、红外管之类的东西在配置里有写，我只需要测试一下就好。&lt;/p&gt;
&lt;p&gt;不过关于扬声器GPIO，我一开始以为它是信号输出引脚，可实际是功放的开关，让我研究了半天。&lt;/p&gt;
&lt;h2 id="优化调整"&gt;优化调整&lt;a class="headerlink" href="#优化调整" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="云台活动范围"&gt;云台活动范围&lt;a class="headerlink" href="#云台活动范围" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;云台电机的步进角为&lt;code&gt;5.625/64&lt;/code&gt;，转一周是4096步，搞不懂为什么厂家固件参数给到了4200，实在难绷，余量留太多。&lt;/p&gt;
&lt;p&gt;于是我根据限位器的角度测算了大致活动范围的步进数，应该差不多了，没必要转到底。&lt;/p&gt;
&lt;h3 id="夜间模式自动切换"&gt;夜间模式自动切换&lt;a class="headerlink" href="#夜间模式自动切换" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;当前Thingino的日夜自动切换由prudynt控制，原理是观察sensor的exposure并与预设阈值判断。算是一种非常简单的防抖/滤波算法。就是……exsposure的范围太小了啦！1万哪里够啊，我这边最大值都56万了！通常数值是5万上下！&lt;/p&gt;
&lt;p&gt;所以就，想办法把这个值的合法范围扩大到了100万。&lt;/p&gt;
&lt;h2 id="结语"&gt;结语&lt;a class="headerlink" href="#结语" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;也没什么好说的，就是逐渐熟悉项目，逐渐适应项目，发现一个又一个问题，解决一个又一个问题。不久之后会忘光的吧，所以写点东西记录一下。&lt;/p&gt;
&lt;p&gt;在这里丢一个&lt;a href="https://techie-s.work/posts/2026/01/port-thingino-to-my-webcam/thingino-linkintec_sm_yt40_t40xp_mis4001_atbm6012bx.bin.zst"&gt;编译好的固件&lt;/a&gt;。目前只有一个摄像头能用，而且禁用了Telegram Bot。录像够用，动作检测也是，够用。&lt;/p&gt;
&lt;h2 id="相关链接"&gt;相关链接&lt;a class="headerlink" href="#相关链接" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/McMCCRU/SNANDer"&gt;https://github.com/McMCCRU/SNANDer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/themactep/thingino-firmware"&gt;https://github.com/themactep/thingino-firmware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/themactep/prudynt-t/tree/stable"&gt;https://github.com/themactep/prudynt-t/tree/stable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hyx0329/thingino-firmware/tree/ly_sm_yt40"&gt;https://github.com/hyx0329/thingino-firmware/tree/ly_sm_yt40&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="Misc"/><category term="thingino"/><category term="ipc"/><category term="ingenic"/><category term="embedded"/><category term="buildroot"/></entry><entry><title>家里云网络基础设施</title><link href="https://techie-s.work/posts/2025/07/homelab-network-infrastructure/" rel="alternate"/><published>2025-07-20T17:55:46+08:00</published><updated>2025-12-19T12:40:08+08:00</updated><author><name/></author><id>tag:techie-s.work,2025-07-20:/posts/2025/07/homelab-network-infrastructure/</id><summary type="html">&lt;p&gt;&lt;em&gt;不是哥们，你真的需要读这个？好好好，&lt;del&gt;看来你的需求和我一样扭曲&lt;/del&gt;。&lt;/em&gt;&lt;/p&gt;&lt;p&gt;开门见山，我想要内网可控的DNS、CA、双栈网络，让每一个内 …&lt;/p&gt;</summary><content type="html">
&lt;h2 id="前言"&gt;前言&lt;a class="headerlink" href="#前言" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;不是哥们，你真的需要读这个？好好好，&lt;del&gt;看来你的需求和我一样扭曲&lt;/del&gt;。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;开门见山，我想要内网可控的DNS、CA、双栈网络，让每一个内网主机/服务都能有任意数量的FQDN用。在此基础上能有一定流量统计能力就好。&lt;/p&gt;
&lt;p&gt;我曾用ArchLinux搓了个路由器，使用的是N3450 CPU的矿渣。那时候iptables仍是大众首选。
因为种种维护难题、加法做太多（All in Boom），这套系统最终被淘汰，而我也转向使用专用路由器/固件。
但是SOHO级路由器的能力……懂的都懂。
我也试过往OpenWRT里丢一些专用的DNS、DHCP配置，让内网访问更便捷，但经常出现奇奇怪怪的小问题。
虽然可靠性不是问题、不会断网，但用着总感觉很憋屈。
我内网有不少依赖内部域名的服务，需要更可靠、更灵活的DNS。&lt;/p&gt;
&lt;p&gt;这次使用某个ARM开发板构建自己的家里云网络核心路由器，OS选用Armbian。
为什么这次不用ArchLinux？因为我不打算追新，也不想自己单独维护一份内核，或者引入什么莫名奇妙的兼容问题（没错我遇到过）。
实话说我不怎么喜欢Debian的一些设计偏好，不过习惯了也还行。&lt;/p&gt;
&lt;p&gt;这是我使用Armbian构建家庭路由器的过程整理，并不包含中间踩坑调试过程。实际操作过程中，会有不少相互耦合的服务。开始吧！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;欢迎电邮提问&amp;amp;留言！（不过M$挺容易吃邮件的啊……）&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="操作环境"&gt;操作环境&lt;a class="headerlink" href="#操作环境" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;某ARM开发板&lt;/li&gt;
&lt;li&gt;至少要有两个网口，有三个会轻松不少&lt;/li&gt;
&lt;li&gt;Armbian(Debian Bookworm)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="筋骨网络划分"&gt;筋骨：网络划分&lt;a class="headerlink" href="#筋骨网络划分" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;真的有人这么划分家庭网络么……&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;首先需要明白自己需要何种形式的网络结构。我的家里云设计比较扁平：使用一台核心路由器、网管交换机，划分3个VLAN，分别用于网络基建、家庭、实验网络。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;- 调制解调器接入设备（光猫也是猫 (^ΦωΦ^) ）
|
- 路由器（本文重点）
|
- 网管交换机
  \_ Infra(wired)
  |_ Home(wired &amp;amp; AP)
  |_ Lab(wired)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Armbian默认采用Netplan配置网络。Netplan会为配置好的后端（systemd-networkd和NetworkManager）动态生成运行时配置。以systemd-networkd为例，系统默认配置位于&lt;code&gt;/usr/lib/systemd/network&lt;/code&gt;，Netplan生成的配置会放在&lt;code&gt;/run/systemd/network&lt;/code&gt;，而用户可以在&lt;code&gt;/etc/systemd/network&lt;/code&gt;自由编写自己的特殊配置、配置覆盖等等。应用优先级&lt;strong&gt;从低到高&lt;/strong&gt;依次为&lt;code&gt;/usr/lib&lt;/code&gt;、&lt;code&gt;/run&lt;/code&gt;、&lt;code&gt;/etc&lt;/code&gt;。因此在配置合理的情况下，Netplan不会和用户自定义的systemd-networkd配置产生冲突。&lt;/p&gt;
&lt;p&gt;有了Netplan，我的网络配置可以非常declarative：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/netplan/60-my-network-structure.yaml&lt;/span&gt;
&lt;span class="nt"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;networkd&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;ethernets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;enmetaport0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# MAC address matcher is more robust&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"en*"&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;macaddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"00:00:00:aa:bb:cc"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# set a custom interface name&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# take care of the prefix, so the rule can be matched again when&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# re-applying the config&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;set-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;enmetaport0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;vlans&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;vlan-infra&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;enmetaport0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"10.101.0.1/16"&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"fcf4:abcd::1/112"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;vlan-home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;enmetaport0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"10.102.0.1/16"&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"fcf4:aeae::1/64"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;vlan-lab&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;23&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;enmetaport0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"10.123.0.1/16"&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"fcf4:dfdf::1/64"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;之后&lt;code&gt;netplan try&lt;/code&gt;试试看，没问题就&lt;code&gt;netplan apply&lt;/code&gt;即时应用，当然重启一下也行。&lt;/p&gt;
&lt;h2 id="杂音pppoe接入isp"&gt;杂音：PPPoE接入ISP&lt;a class="headerlink" href="#杂音pppoe接入isp" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;听说PPPoE太老了不适合云原生所以没有存在的价值。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Netplan和systemd-networkd并不支持对PPP上网的直接配置。好在这部分手写也不是问题。&lt;/p&gt;
&lt;h3 id="配置上游接口"&gt;配置上游接口&lt;a class="headerlink" href="#配置上游接口" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在netplan里激活需要用到的上游接口，这样PPP服务脚本那里可以省掉启用接口的步骤：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/netplan/60-pppoe-interface.yaml&lt;/span&gt;
&lt;span class="nt"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;networkd&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;ethernets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;enupstream0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# it's a different port/interface!&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"en*"&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;macaddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"00:00:00:aa:bb:cd"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;set-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;enupstream0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以先&lt;code&gt;netplan apply&lt;/code&gt;一下。后面测试PPP需要用到这个接口。&lt;/p&gt;
&lt;h3 id="配置ppp服务"&gt;配置PPP服务&lt;a class="headerlink" href="#配置ppp服务" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;首先安装ppp包：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;ppp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;配置PPP服务。要写一份PPP配置及一份systemd服务脚本。&lt;/p&gt;
&lt;h4 id="ppp配置"&gt;PPP配置&lt;a class="headerlink" href="#ppp配置" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;先来看PPP配置&lt;code&gt;/etc/ppp/peers/enupstream0&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# /etc/ppp/peers/enupstream0
# debug mode
#debug

user "your account name / phone number"
password "your account password"

# bind interface
plugin rp-pppoe.so enupstream0

# Ensure a PID entry with specified linkname is made pppoe
# e.g. /run/ppp-enupstream0.pid
linkname enupstream0

# specifiy interface number, e.g. ppp0, ppp101
unit 101

usepeerdns
persist
# retry 10 times before reporting error(exit)
maxfail 10
defaultroute
hide-password
noauth
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我的配置文件命名和网络接口一样，这是我的习惯，同时也方便后面的服务脚本套用这个名字。
上面直接将密码写在了配置中，并没有遵循一些文章的推荐方式。我的上游用的是PAP验证，而非CHAP，若按推荐配置将密码存放到单独文件，还会因为验证配置文件名不同踩坑。总体上直接写单独一个配置里更好维护（家宽也不怕漏密码）。&lt;/p&gt;
&lt;p&gt;之前运行过&lt;code&gt;netplan apply&lt;/code&gt;的话，这里可以运行&lt;code&gt;pon enupstream0&lt;/code&gt;启用服务测试一下。如果路由器自己能上网，那PPPoE大概没什么问题了。随后可以&lt;code&gt;poff&lt;/code&gt;停止pppd服务，因为后面要用systemd让pppd成为一个正经的服务。&lt;/p&gt;
&lt;h4 id="让pppd成为一个正经的systemd服务"&gt;让pppd成为一个正经的systemd服务&lt;a class="headerlink" href="#让pppd成为一个正经的systemd服务" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;写个服务脚本，网上抄的加点佐料，绑定对应网络接口和配置文件：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/systemd/system/pppd@.service&lt;/span&gt;
&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;PPP link to %I&lt;/span&gt;
&lt;span class="na"&gt;BindsTo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sys-subsystem-net-devices-$I.device&lt;/span&gt;
&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sys-subsystem-net-devices-$I.device&lt;/span&gt;
&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;systemd-networkd.service&lt;/span&gt;

&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;notify&lt;/span&gt;
&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/sbin/pppd call %I nodetach up_sdnotify&lt;/span&gt;
&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="na"&gt;RestartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;30&lt;/span&gt;

&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后可以用&lt;code&gt;systemctl enable --now pppd@enupstream0&lt;/code&gt;启用这个pppd服务并激活自启动。&lt;/p&gt;
&lt;h3 id="获取ipv6并分配前缀"&gt;获取IPv6并分配前缀&lt;a class="headerlink" href="#获取ipv6并分配前缀" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;我的ISP提供了一个&lt;code&gt;::/60&lt;/code&gt;前缀，可以直接通过DHCPv6/RA来获得，并不涉及PPPoE，但是相关配置需对应PPP的接口。之前ppp配置里写的是&lt;code&gt;unit 101&lt;/code&gt;，那这里的接口就是&lt;code&gt;ppp101&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;先启用&lt;code&gt;ppp101&lt;/code&gt;上的DHCPv6，虽说&lt;code&gt;ppp101&lt;/code&gt;其实也不需要前缀，但下列配置最后一节还是给它分配了个：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/systemd/network/30-ppp101-ipv6.network&lt;/span&gt;
&lt;span class="k"&gt;[Match]&lt;/span&gt;
&lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ppp101&lt;/span&gt;
&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ppp&lt;/span&gt;

&lt;span class="k"&gt;[Network]&lt;/span&gt;
&lt;span class="na"&gt;DHCP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ipv6&lt;/span&gt;
&lt;span class="na"&gt;DefaultRouteOnDevice&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="c1"&gt;# necessary, to avoid messing with interface&lt;/span&gt;
&lt;span class="na"&gt;KeepConfiguration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;static&lt;/span&gt;
&lt;span class="na"&gt;LinkLocalAddressing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ipv6&lt;/span&gt;
&lt;span class="na"&gt;IPv6AcceptRA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;

&lt;span class="k"&gt;[DHCP]&lt;/span&gt;
&lt;span class="na"&gt;RouteMetric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;100&lt;/span&gt;
&lt;span class="na"&gt;UseMTU&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;

&lt;span class="k"&gt;[DHCPv6]&lt;/span&gt;
&lt;span class="c1"&gt;# to hide our real hostname :P, explictly set one&lt;/span&gt;
&lt;span class="na"&gt;Hostname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;gate&lt;/span&gt;

&lt;span class="c1"&gt;# Don't use most of the other DHCP info&lt;/span&gt;
&lt;span class="k"&gt;[DHCPv6]&lt;/span&gt;
&lt;span class="c1"&gt;#UseAddress=false&lt;/span&gt;
&lt;span class="c1"&gt;#UseDNS=false&lt;/span&gt;
&lt;span class="na"&gt;UseNTP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;UseHostname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;UseDomains&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;

&lt;span class="c1"&gt;# The lines below are optional, to also assign an address in the delegated prefix&lt;/span&gt;
&lt;span class="c1"&gt;# to the upstream interface.&lt;/span&gt;
&lt;span class="k"&gt;[Network]&lt;/span&gt;
&lt;span class="na"&gt;KeepConfiguration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;
&lt;span class="na"&gt;DHCPPrefixDelegation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;
&lt;span class="k"&gt;[DHCPPrefixDelegation]&lt;/span&gt;
&lt;span class="na"&gt;UplinkInterface&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;:self&lt;/span&gt;
&lt;span class="na"&gt;SubnetId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;
&lt;span class="na"&gt;Announce&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;随后即可为我的实验网接口分配一个&lt;code&gt;::/64&lt;/code&gt;前缀，还是公网，足够我的小网络用了。此配置是对netplan生成配置的自定义覆盖（注意文件路径）。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/systemd/network/10-netplan-vlan-lab.network.d/ipv6-pd.conf&lt;/span&gt;
&lt;span class="k"&gt;[Network]&lt;/span&gt;
&lt;span class="c1"&gt;# we are the router, ignore others&lt;/span&gt;
&lt;span class="na"&gt;IPv6AcceptRA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;
&lt;span class="na"&gt;DHCPv6PrefixDelegation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;

&lt;span class="c1"&gt;# Uncomment below if networkd should handle DHCP and RA&lt;/span&gt;
&lt;span class="c1"&gt;# e.g. you don't use dnsmasq as the DHCP server&lt;/span&gt;
&lt;span class="c1"&gt;#[Network]&lt;/span&gt;
&lt;span class="c1"&gt;#IPv6SendRA=yes&lt;/span&gt;
&lt;span class="c1"&gt;#DHCPServer=yes&lt;/span&gt;

&lt;span class="c1"&gt;# Delegated prefix&lt;/span&gt;
&lt;span class="c1"&gt;# use :auto or specify the interface, e.g. ppp0, ppp101&lt;/span&gt;
&lt;span class="k"&gt;[DHCPPrefixDelegation]&lt;/span&gt;
&lt;span class="na"&gt;UplinkInterface&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ppp101&lt;/span&gt;
&lt;span class="na"&gt;SubnetId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;Announce&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;
&lt;span class="na"&gt;Token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;::1&lt;/span&gt;

&lt;span class="c1"&gt;# Optional custom local prefix&lt;/span&gt;
&lt;span class="k"&gt;[IPv6Prefix]&lt;/span&gt;
&lt;span class="na"&gt;Prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;fcf4:dfdf::/64&lt;/span&gt;
&lt;span class="na"&gt;Assign&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;
&lt;span class="na"&gt;Token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;::1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;此时用&lt;code&gt;systemctl restart systemd-networkd&lt;/code&gt;来重载配置。如果内网lab接口获取到了前缀，就说明配置成功啦！&lt;/p&gt;
&lt;h2 id="连结做个路由器吧"&gt;连结：做个路由器吧！&lt;a class="headerlink" href="#连结做个路由器吧" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Linux路由器，宁有种乎？&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;前面配置好后，Armbian并不会直接变成路由器的样子。为了对下游网络提供服务，需要开启转发功能、启用DHCP/DNS服务等等操作。&lt;/p&gt;
&lt;h3 id="开启路由转发"&gt;开启路由转发&lt;a class="headerlink" href="#开启路由转发" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;需要打开一些内核功能。添加一个新的sysctl配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# /etc/sysctl.d/90-enable-ip-forwarding.conf
# This is IP forwarding
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;随后刷新配置（参考&lt;code&gt;/etc/sysctl.d/README.sysctl&lt;/code&gt;）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;service&lt;span class="w"&gt; &lt;/span&gt;procps&lt;span class="w"&gt; &lt;/span&gt;force-reload
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;转发功能启用口扌立！&lt;/p&gt;
&lt;h3 id="用dnsmasq作dhcpdns服务器"&gt;用dnsmasq作DHCP/DNS服务器&lt;a class="headerlink" href="#用dnsmasq作dhcpdns服务器" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;由于我的网络存在多个下游接口、划分了多个子网，同时开启多个dnsmasq实例是更合适的选择。此处以lab网络为例，介绍启用多dnsmasq实例的方法。&lt;/p&gt;
&lt;p&gt;我计划将实例配置安置在&lt;code&gt;/etc/dnsmasq.instance.d&lt;/code&gt;。我需要按Debian的风格为每个实例配置好配置文件所在目录：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 总之先干掉单独的dnsmasq服务&lt;/span&gt;
systemctl&lt;span class="w"&gt; &lt;/span&gt;mask&lt;span class="w"&gt; &lt;/span&gt;dnsmasq

&lt;span class="c1"&gt;# 为vlan-lab实例编写基本配置&lt;/span&gt;
&lt;span class="c1"&gt;# 类似/etc/default/dnsmasq，不过仅保留了配置目录设置&lt;/span&gt;
cat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt; EOF &amp;gt; /etc/default/dnsmasq.vlan-lab&lt;/span&gt;
&lt;span class="s"&gt;# setup config dir, and config blacklists&lt;/span&gt;
&lt;span class="s"&gt;CONFIG_DIR=/etc/dnsmasq.instance.d/vlan-lab,.dpkg-dist,.dpkg-old,.dpkg-new&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;

&lt;span class="c1"&gt;# 激活服务开机自启动，但是先别启动&lt;/span&gt;
systemctl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dnsmasq@vlan-lab
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后点一份dnsmasq配置。dnsmasq必须只绑定到特定接口，并避免回环接口，不然会占用端口资源，导致其它实例无法启动。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# /etc/dnsmasq.instance.d/vlan-lab/main.conf
# Net prefix delegation is handled by systemd-networkd
# Dnsmasq only handles address allocation(DHCP) and hostname resolution
port=53
interface=vlan-lab
# do not bind loopback, otherwise other instance will fail to start
except-interface=lo
# the static addresses given to vlan-lab
listen-address=10.123.0.1
listen-address=fcf4:dfdf::1
#bind-interfaces
bind-dynamic

#no-hosts
#addn-hosts=/etc/banner_add_hosts
#expand-hosts
domain=lab
local=/lab/
resolv-file=/etc/resolv.conf

# IPv4
dhcp-range=10.123.100.0,10.123.200.0,255.255.0.0,1h
# router, DHCPv4, btw IPv6 uses RA not DHCPv6
# 0.0.0.0 is a special address referencing the machine dnsmasq runs on
dhcp-option=3,0.0.0.0
# DNS
dhcp-option=6,0.0.0.0

# IPv6
enable-ra
# custom prefix
dhcp-range=fcf4:dfdf::1:0, fcf4:dfdf::ffff:ffff, slaac, 64, 1h
# the prefix previously delegated
dhcp-range=::1:0, ::ffff:ffff, constructor:vlan-lab, slaac, 64, 1h
# RA on vlan-lab, interval 30 seconds, router lifetime is 1200
ra-param=vlan-lab,30,1200
# DNS, it will make the firewall rules very difficult to write if [::] is used,
# since one interface can have multiple independent IPv6 addresses.
dhcp-option=option6:dns-server,fcf4:dfdf::1

# Never forward plain names (without a dot or domain part)
domain-needed
# Never forward addresses in the non-routed address spaces.
bogus-priv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;随后可以启动服务了：&lt;code&gt;systemctl start dnsmasq@vlan-lab&lt;/code&gt;。其它实例的配置方法相同。DHCP能自动分发配置，我可太喜欢它了。&lt;/p&gt;
&lt;h3 id="开启nat"&gt;开启NAT&lt;a class="headerlink" href="#开启nat" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;内网设备用内网地址无法直接上网，需要NAT。这一步用nftables实现。可以在&lt;code&gt;/etc/nftables.conf&lt;/code&gt;中添加下面的内容：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;table inet simple_srcnat
flush table inet simple_srcnat
delete table inet simple_srcnat
table inet simple_srcnat {

    define INTERNAL_NORMAL_IFS = {
        "vlan-home",
        "vlan-lab",
    }

    define INTERNAL_CRITICAL_IFS = {
        "vlan-infra"
    }

    define INTERNAL_IFS = {
        $INTERNAL_NORMAL_IFS,
        $INTERNAL_CRITICAL_IFS,
    }

    define OUTGOING_IFS = {
        "enupstream0",
        "ppp101"
    }

    chain forward {
        type filter hook forward priority mangle; policy accept;

        # MSS clamp
        iifname "ppp101" tcp flags syn tcp option maxseg size set rt mtu
        oifname "ppp101" tcp flags syn tcp option maxseg size set rt mtu
    }

    chain postrouting {
        type nat hook postrouting priority srcnat; policy accept;

        oifname $INTERNAL_CRITICAL_IFS drop

        # masquerade local addresses
        oifname $OUTGOING_IFS ip saddr 10.0.0.0/8 masquerade
        oifname $OUTGOING_IFS ip6 saddr fcf4::/16 masquerade
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;使用&lt;code&gt;nft -f /etc/nftables.conf&lt;/code&gt;加载配置。&lt;/p&gt;
&lt;p&gt;简单说，上面的table启用了&lt;code&gt;vlan-home&lt;/code&gt;和&lt;code&gt;vlan-lab&lt;/code&gt;上所有内网地址的NAT，丢弃所有外部主动发往基建网络&lt;code&gt;vlan-infra&lt;/code&gt;的包，并在ppp出口应用MSS钳制。&lt;/p&gt;
&lt;p&gt;有了NAT，下游设备就能通过路由器中继上网啦。&lt;/p&gt;
&lt;h2 id="我的名字我做主掌控自己的dns"&gt;我的名字我做主：掌控自己的DNS&lt;a class="headerlink" href="#我的名字我做主掌控自己的dns" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;姓名能给人力量，但也是强大的束缚。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;改造网络很大一部分原因是要设立自己的私有DNS服务器，以便灵活建设内网服务。我选了&lt;code&gt;home.internal&lt;/code&gt;做内网服务主要使用的域。&lt;/p&gt;
&lt;h3 id="设立自己的权威dns之knot-dns"&gt;设立自己的权威DNS之Knot DNS&lt;a class="headerlink" href="#设立自己的权威dns之knot-dns" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.knot-dns.cz/"&gt;Knot DNS&lt;/a&gt;是一款开源的权威DNS服务软件。它不会像dnsmasq、smartdns之类向上游递归查询。用它就可以设立自己的内网顶级域名。为了保证兼容性，我打算拿下整个&lt;code&gt;.internal&lt;/code&gt;顶级域名（事实是如果只拿部分二级域名，中间的递归DNS服务还是会把请求发到外部的根DNS去）。&lt;/p&gt;
&lt;p&gt;Knot DNS服务配置，建议结合&lt;a href="https://www.knot-dns.cz/docs/3.4/html/configuration.html"&gt;Knot DNS配置文档&lt;/a&gt;一同阅读。简单说，配置了监听端口，各个二级域名配置文件的位置，以及管理DNS的key。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/knot/knot.conf&lt;/span&gt;

&lt;span class="c1"&gt;# server listen at 5553&lt;/span&gt;
&lt;span class="nt"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;rundir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/run/knot"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;knot:knot&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;0.0.0.0@5553&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;::&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;5553&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;

&lt;span class="nt"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;syslog&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;info&lt;/span&gt;

&lt;span class="nt"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/var/lib/knot"&lt;/span&gt;

&lt;span class="c1"&gt;# some keys for standard DNS operations&lt;/span&gt;
&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;naive&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;algorithm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;hmac-sha512&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;generate-your-own!&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;home&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;algorithm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;hmac-sha512&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;generate-your-own!&lt;/span&gt;

&lt;span class="c1"&gt;# access control, for safety&lt;/span&gt;
&lt;span class="nt"&gt;acl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;deny_all&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;on&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;address_only&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;update&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;update-type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;AAAA&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;naive_key&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;update&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;naive&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;home_key&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;update&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;home&lt;/span&gt;

&lt;span class="c1"&gt;# zone template. zones defined in `zone:` can use templates here and bring&lt;/span&gt;
&lt;span class="c1"&gt;# their own overrides&lt;/span&gt;
&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;default&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/var/lib/knot"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"%s.zone"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;acl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;address_only&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;acl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;naive_key&lt;/span&gt;

&lt;span class="nt"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;INTERNAL&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;acl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;deny_all&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;infra.INTERNAL&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;acl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;deny_all&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;home.INTERNAL&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;acl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;address_only&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;acl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;home_key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;生成key可以用&lt;code&gt;keymgr&lt;/code&gt;，会和Knot DNS一起装好，举个例子（别用这个key！）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# keymgr -t home hmac-sha512
# hmac-sha512:home:cVE3CZ0twnAmf6C7suhr8nJQTtdqo8RVmJkOC0WL5luandWazPrY7AtQJAEkE86HwzOcTla187fInNYZ5KkpPg==
key:
  - id: home
    algorithm: hmac-sha512
    secret: cVE3CZ0twnAmf6C7suhr8nJQTtdqo8RVmJkOC0WL5luandWazPrY7AtQJAEkE86HwzOcTla187fInNYZ5KkpPg==
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;key的作用是管理DNS记录。如果没有相关需求，其实也可以忽略。不过我后面需要自己设立一个内网DDNS服务。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;为了让DNS工作正常，还需要添加SOA记录。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# /var/lib/knot/internal.zone
internal.       30      SOA     ns.infra.internal.  nomail.ns.infra.internal. 2025071601 3600 15 86400 3600
internal.       30      NS      ns.infra.internal.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# /var/lib/knot/infra.internal.zone
infra.internal.       30      SOA     ns.infra.internal.  nomail.ns.infra.internal. 2025071601 3600 15 86400 3600
infra.internal.       30      NS      ns.infra.internal.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# /var/lib/knot/home.internal.zone
home.internal.       30      SOA     ns.infra.internal.  nomail.ns.infra.internal. 2025071601 3600 15 86400 3600
home.internal.       30      NS      ns.infra.internal.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;虽然配置好了权威DNS，但实际上内网设备还不会用它，需要别的伙伴帮一把。&lt;/p&gt;
&lt;h3 id="用smartdns做中间件分流域名解析"&gt;用smartdns做中间件分流域名解析&lt;a class="headerlink" href="#用smartdns做中间件分流域名解析" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;做就要做彻底。要避免内网DNS请求发送到意外的地方去，就让smartdns处理好分流。&lt;/p&gt;
&lt;p&gt;先开个smartdns在5552端口上，将内网域名和公网域名分流到各自的DNS，同时让smartdns自己应答：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# /etc/smartdns/smartdns.conf
# smartdns configuration

bind [::]:5552

# this name is the name of this server
# the optimal address of current host will be sent if querying this one
server-name ns.infra.internal
# same with above, but can be specified multiple times
ddns-domain anything.ns.infra.internal

server 119.29.29.29
server 223.5.5.5

# internal zone is handled by knot at 5553
server 127.0.0.1:5553 -group internal_knot -exclude-default-group

### Support wildcard or main domain matching
### /*.example.com/ (does not include example.com)
### /example.com/ (include example.com)
### also read: https://github.com/pymumu/smartdns/issues/1659
# including: infra.internal, home.internal
nameserver /internal/internal_knot
# somehow setting rr-ttl-min is required, otherwise the ttl will be set to 600
domain-rules /internal/ -no-cache -no-serve-expired -response-mode fastest-response -rr-ttl-min 30 -speed-check-mode none
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;再用nftables把内网指向本机53的流量劫持过来（不然端口分配有得烦），用IP来判断是不是发给内网的DNS请求：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# in /etc/nftables.conf
table inet simple_port_forward
flush table inet simple_port_forward
delete table inet simple_port_forward
table inet simple_port_forward {
    define INTERNAL_INTERFACES = {
        "vlan-home",
        "vlan-lab",
        "vlan-infra"
    }

    chain local_hijack {
        # Hijack DNS requests sent to this router
        # redirect dns requests to smartdns instead of dnsmasq
        # smartdns will forward back per subnet resolution to dnsmasq
        tcp dport 53 redirect to :5552
        udp dport 53 redirect to :5552
    }

    chain prerouting {
        # priority: dstnat
        type nat hook prerouting priority -100 ;

        # internal requests sent to this router
        iifname $INTERNAL_INTERFACES ip daddr &amp;amp; 255.0.255.255 == 10.0.0.1 jump local_hijack
        iifname $INTERNAL_INTERFACES ip6 daddr &amp;amp; ffff::ffff:ffff:ffff:ffff == fcf4::1 jump local_hijack
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;使用&lt;code&gt;nft -f /etc/nftables.conf&lt;/code&gt;加载配置。&lt;/p&gt;
&lt;p&gt;这样smartdns就全权负责局域网的所有DNS请求啦，一切尽在掌握！&lt;/p&gt;
&lt;p&gt;注意当经由路由器上的dnsmasq向smartdns请求所配置的&lt;code&gt;ddns-domain&lt;/code&gt;、&lt;code&gt;server-name&lt;/code&gt;时会让结果变成回环地址，所以&lt;strong&gt;不建议&lt;/strong&gt;让dnsmasq做下游直接和客户端交互。&lt;/p&gt;
&lt;h4 id="让本机的systemd-resolved也转发internal-tld请求到自己的knot-dns"&gt;让本机的systemd-resolved也转发internal tld请求到自己的Knot DNS&lt;a class="headerlink" href="#让本机的systemd-resolved也转发internal-tld请求到自己的knot-dns" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;因为netplan/systemd-networkd，路由器自己默认用的DNS服务器是systemd-resolved，本机要特殊考虑一下。&lt;/p&gt;
&lt;p&gt;新增配置文件，让路由器自己也用smartdns查询：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/systemd/resolved.conf.d/dns_internal.conf&lt;/span&gt;
&lt;span class="k"&gt;[Resolve]&lt;/span&gt;
&lt;span class="na"&gt;DNS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:5552&lt;/span&gt;
&lt;span class="na"&gt;Domains&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;~.internal.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="部分保留dnsmasq的主机名查询能力"&gt;部分保留dnsmasq的主机名查询能力&lt;a class="headerlink" href="#部分保留dnsmasq的主机名查询能力" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;只需而且只能保留本地域名的查询能力，毕竟我们有smartdns了，还要避免干扰它。&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;/etc/smartdns/smartdns.conf&lt;/code&gt;中添加下面的内容，将局域网域名的查询分流到各个dnsmasq。注意IP地址要匹配，之前配置的dnsmasq相当于直接绑了IP。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# in /etc/smartdns/smartdns.conf
# Some requests are forwarded to related dnsmasq instance

#server 10.101.0.1:53 -group dq_infra -exclude-default-group
server 10.102.0.1:53 -group dq_home -exclude-default-group
server 10.123.0.1:53 -group dq_lab -exclude-default-group

domain-rules /home/ -g dq_home -no-cache -no-serve-expired
nameserver /home/dq_home
domain-rules /lab/ -g dq_lab -no-cache -no-serve-expired
nameserver /lab/dq_lab
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="设立自己的ddns服务"&gt;设立自己的DDNS服务&lt;a class="headerlink" href="#设立自己的ddns服务" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;直接用TSIG key对客户端的要求有点高，略微麻烦。看到别人自己搓了个restful API的DDNS服务，我也搓了一个，从http请求中获取key和需要更新的域名、并直接取请求来源的IP地址，自动更新域名信息。对于我的内网来说，足够好用了。我的参考实现：&lt;a href="https://techie-s.work/posts/2025/07/homelab-network-infrastructure/ddns-server.tar.gz"&gt;ddns-server.py&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;参考Config：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# config.toml
knot_key = "hmac-sha512:home:your_key_data“
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;参考更新指令：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# generate a new token, can be reused later
curl http://ns.infra.internal:5561/new_token/prefix.home.internal/the_super_token
# update DNS A record, the new ip address is the request source address
curl -4 http://ns.infra.internal:5561/update_ddns/test_name/the_token
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;都给完整源码了，看两眼呗，也不长不是么。&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="后备隐藏能源用nut管理ups"&gt;后备隐藏能源：用NUT管理UPS&lt;a class="headerlink" href="#后备隐藏能源用nut管理ups" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Backup UPS可不是Backup！&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;在连接UPS的机器上装&lt;code&gt;nut&lt;/code&gt;，在被UPS保护的机器上装&lt;code&gt;nut-client&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;nut的默认配置已经能实现低电量自动关机，在配置好nut-server和upsmon监控的上游后，不需要额外配置upssched之类的东西。&lt;/p&gt;
&lt;p&gt;upsmon的脚本可选，用于手动处理某些UPS事件，除非和其它系统集成，通常用不上，毕竟服务都有systemd之类的东西管理。&lt;/p&gt;
&lt;p&gt;不过需要写个关机脚本自动关断UPS，这个脚本没在软件包里。Armbian用systemd，所以可以照抄下面的脚本（flag文件的位置要改）。UPS会在收到关机请求后等待所设置的delay后再关闭。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c1"&gt;# this file is /lib/systemd/system-shutdown/nut-killpower&lt;/span&gt;
&lt;span class="c1"&gt;# do remember to chmod 755 on it&lt;/span&gt;
&lt;span class="c1"&gt;# read `man systemd-poweroff.service` for more information&lt;/span&gt;

&lt;span class="c1"&gt;# set accordingly, see /etc/nut/upsmon.conf&lt;/span&gt;
&lt;span class="nv"&gt;KILL_POWER_FLAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/run/nut/killpower
&lt;span class="c1"&gt;# action is either "halt", "poweroff", "reboot" or "kexec"&lt;/span&gt;
&lt;span class="nv"&gt;ACTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"_&lt;/span&gt;&lt;span class="nv"&gt;$ACTION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"_poweroff"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KILL_POWER_FLAG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
upsdrvctl&lt;span class="w"&gt; &lt;/span&gt;shutdown
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="结语懒得写了"&gt;结语：懒得写了&lt;a class="headerlink" href="#结语懒得写了" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;后续我进一步部署了Step CA、node_exporter+Prometheus等服务，适当完善了防火墙配置。内网ACME HTTPS和简单的流量统计也都有了。&lt;/p&gt;
&lt;p&gt;在具体的实施中，有些配置细节，例如配置文件拆分（nftables、smartdns），想全部写到文章里显得冗杂。&lt;/p&gt;
&lt;p&gt;家里云搞那么复杂干嘛啊，哎呀不写了不写了。&lt;/p&gt;
&lt;h2 id="主要参考资料"&gt;主要参考资料&lt;a class="headerlink" href="#主要参考资料" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://lars.hupel.info/articles/personal-dyndns/&lt;/li&gt;
&lt;li&gt;https://blog.sww.moe/post/soft-router-network&lt;/li&gt;
&lt;li&gt;https://major.io/p/dhcpv6-prefix-delegation-with-systemd-networkd/&lt;/li&gt;
&lt;li&gt;https://wiki.debian.org/IPv6PrefixDelegation&lt;/li&gt;
&lt;li&gt;https://wiki.archlinux.org/title/Router&lt;/li&gt;
&lt;li&gt;https://briandouglas.ie/sqlite-defaults/&lt;/li&gt;
&lt;/ul&gt;</content><category term="Misc"/><category term="router"/><category term="dns"/><category term="intranet"/><category term="armbian"/></entry><entry><title>为Pixel 4a开启DeviceAsWebcam功能</title><link href="https://techie-s.work/posts/2025/01/enable-device-as-webcam-for-pixel4a/" rel="alternate"/><published>2025-01-10T22:37:23+08:00</published><updated>2025-01-19T12:55:59+08:00</updated><author><name/></author><id>tag:techie-s.work,2025-01-10:/posts/2025/01/enable-device-as-webcam-for-pixel4a/</id><summary type="html">&lt;p&gt;反正已经在自行修改编译Android系统了，目的就是围绕自己的需求做功能改进。
功能多多益善嘛。尽管本来只是想实现USB NCM网络共享……&lt;/p&gt;&lt;p&gt;&lt;del&gt;记录&lt;/del&gt; 发泄 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;反正已经在自行修改编译Android系统了，目的就是围绕自己的需求做功能改进。
功能多多益善嘛。尽管本来只是想实现USB NCM网络共享……&lt;/p&gt;
&lt;p&gt;&lt;del&gt;记录&lt;/del&gt; 发泄一下頭の痛み……&lt;/p&gt;

&lt;h2 id="切入点几乎是无头苍蝇"&gt;切入点？几乎是无头苍蝇&lt;a class="headerlink" href="#切入点几乎是无头苍蝇" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;一开始我只是想把老手机的RNDIS实现改为USB-NCM。恰巧在过程中看到了部分（后证实无用）用configfs配置USB UVC gadget的init脚本、加上调试时看到了UVC组件的警告日志，受到吸引，想为旧手机Pixel 4a实现这个功能，发挥余热。&lt;/p&gt;
&lt;p&gt;我依稀记得支持Oneplus 7的YAAP有实现过相关功能，而两款手机差不多处于同一时代，应该有参考的价值。在GitHub组织项目下乍一搜索，好像没看到什么有趣的东西。但是在搜索引擎上发现了&lt;a href="https://source.android.com/docs/core/camera/webcam"&gt;Android官方的相关文档&lt;/a&gt;。看起来……只要把提到的东西都实现就好？&lt;/p&gt;
&lt;h2 id="一步步探索"&gt;一步步探索&lt;a class="headerlink" href="#一步步探索" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;内核4.14，老东西啊，得慢慢整整。&lt;/p&gt;
&lt;h3 id="linux开启必要的config"&gt;linux开启必要的config&lt;a class="headerlink" href="#linux开启必要的config" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;就是把驱动里有关USB gadget、Function FS以及UVC的部分启用嘛，没什么大不了的！&lt;/p&gt;
&lt;h3 id="启用prop修改init脚本"&gt;启用prop、修改init脚本&lt;a class="headerlink" href="#启用prop修改init脚本" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;起初我以为内核开了相关功能就能自动支持，我错了。&lt;/p&gt;
&lt;p&gt;为启用Device as Webcam功能，需要设置 &lt;code&gt;ro.usb.uvc.enabled=true&lt;/code&gt;，这样相关菜单才会在设置里显示出来。调试的时候我就先用Magisk/KernelSU的模块功能凑合凑合了。&lt;/p&gt;
&lt;p&gt;改init就比较讨厌了，完全没有相关经验，只能凭借自己的编程直觉猜猜猜。事实上由于自己对Android编译系统如何将这些脚本导入并不了解，我围着错误的遗留脚本修改了很久！
为了使用兼容的USB PID/VID，我在GitHub上搜索了相关代码commit，也顺带在yaap的仓库里发现了可以参考的脚本。各位的风格不尽相同，我尽量参考。&lt;/p&gt;
&lt;h3 id="在hal中加入uvc-gadget支持"&gt;在HAL中加入UVC gadget支持&lt;a class="headerlink" href="#在hal中加入uvc-gadget支持" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;官方文档中的案例给的是AIDL HAL的例子，但是Pixel 4a使用的是HIDL。我不清楚如何从HIDL迁移到AIDL，好在发现二者的代码结构类似。最后我以相当hacky的方式在代码中插入旧版接口中不存在的常量/枚举类对应值，强行兼容相关接口。&lt;/p&gt;
&lt;p&gt;不仅要改 &lt;code&gt;device/google/sunfish&lt;/code&gt;，还要改 &lt;code&gt;hardware/google/pixel&lt;/code&gt; 把遗漏的case补上。&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;为什么只是改HIDL而非迁移AIDL？&lt;/p&gt;
&lt;p&gt;看起来Pixel 4a USB HAL的gadget部分用到了私有库，被标记为&lt;code&gt;proprietary&lt;/code&gt;。
目前已经是拆分后的样子，部分USB HAL迁移到了AIDL，但gadget部分仍然是HIDL。
Pixel系列还有另外几部设备状况相同。&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="v4l2节点黑名单"&gt;V4L2节点黑名单&lt;a class="headerlink" href="#v4l2节点黑名单" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;部分系统视频组件（摄像头、编码器等等）也和V4L2一样使用 &lt;code&gt;/dev/videoN&lt;/code&gt; 节点暴露出来，而Device as Webcam服务要找的端点是gadget模块初始化后&lt;strong&gt;新创建的节点&lt;/strong&gt;。服务会顺序遍历所有节点，寻找并选择能输出视频流的节点。为了避免报错（实际摄像头节点被占用）和错用（用了转码节点之类），需要把既有能找到的所有节点屏蔽掉，这就是本节所述黑名单。&lt;/p&gt;
&lt;p&gt;此黑名单可用rro overlay的形式覆盖 &lt;code&gt;com.android.DeviceAsWebcam&lt;/code&gt; 服务暴露的配置。&lt;/p&gt;
&lt;p&gt;说起来我一开始一直以为程序调用的V4L2节点是现有的，浪费了不少时间调试。&lt;/p&gt;
&lt;p&gt;对于Pixel 4a，需在 &lt;code&gt;android_code_root/device/google/sunfish&lt;/code&gt; 添加对应修改，可以仿照既有例子改改、GitHub搜索类似例子。最后的黑名单（&lt;code&gt;res/raw/ignored_v4l2_nodes.json&lt;/code&gt;）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"/dev/video0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"/dev/video1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"/dev/video2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"/dev/video32"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"/dev/video33"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"/dev/video34"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="有预览了但是不认v4l2节点"&gt;有预览了，但是不认V4L2节点&lt;a class="headerlink" href="#有预览了但是不认v4l2节点" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;日志里看不到什么错误，甚至提到打开了相关的File Descriptor。检查服务程序代码发现预览和视频编码传输在两个线程上运行。&lt;/p&gt;
&lt;p&gt;上位机dmesg一直报 &lt;code&gt;Failed to query (GET_DEF) UVC control 2 on unit 2: -110&lt;/code&gt;。超时？怕不是单纯没反应。&lt;/p&gt;
&lt;p&gt;直觉告诉我服务无声忽略了内部错误。&lt;/p&gt;
&lt;h4 id="内存不足内存怎么会不足"&gt;内存不足？内存怎么会不足？&lt;a class="headerlink" href="#内存不足内存怎么会不足" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;在logcat中有发现：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Unable to request V4L2 buffers from gadget driver: Out of memory
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查邻近日志，发现 &lt;code&gt;swiotlb buffer is full&lt;/code&gt;，这又是什么？
在网上寻找到&lt;a href="https://github.com/google-coral/edgetpu/issues/443#issuecomment-901382855"&gt;相关案例&lt;/a&gt;后，明白了这是个USB相关的缓存，默认就16MB，好像会影响一些设备使用。不管了，先试试看，临时提升缓存容量到128MB：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;su&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"echo 128 &amp;gt; /sys/module/usbcore/parameters/usbfs_memory_mb"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;报错消失了，晚点一起丢init脚本里。&lt;/p&gt;
&lt;h4 id="还是不行没有输出"&gt;还是不行？没有输出？&lt;a class="headerlink" href="#还是不行没有输出" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;是我init脚本没写对？
还是我漏了什么配置？&lt;/p&gt;
&lt;p&gt;反复尝试后再度检查服务代码，原来Device as Webcam服务用到了老内核驱动尚未完全实现的VIDIOC ioctl接口。&lt;/p&gt;
&lt;p&gt;怎么办？不知道，但是可以大力出奇迹。查找主线内核相关commit，然后疯狂cherry pick，不好一次合并就多pick一些，多多pick，多多pick……&lt;/p&gt;
&lt;p&gt;唉不是，你这内核怎么有&lt;a href="https://github.com/LineageOS/android_kernel_google_msm-4.14/commit/3a64b65513d6ee77c0bd926c8b143344a05071cf"&gt;非主线的UVC功能实现&lt;/a&gt;？扬了！Revert！&lt;/p&gt;
&lt;p&gt;终于，大力成功奇迹，我也终于让USB有了视频输出。关键commit是&lt;a href="https://github.com/torvalds/linux/commit/588b9e85609bcb2f84a2be83591480aa943943b6"&gt;实现VIDIOC_ENUM_*&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="终于在上位机上看到视频输出了"&gt;终于在上位机上看到视频输出了&lt;a class="headerlink" href="#终于在上位机上看到视频输出了" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;我欣喜若狂，开始pick pick pick更多commit，却不知道危机正在靠近……&lt;/p&gt;
&lt;p&gt;也就pick了数十个UVC gadget相关的commit……也许不算多……&lt;/p&gt;
&lt;h4 id="中途拔usb线就panic"&gt;中途拔USB线就panic？&lt;a class="headerlink" href="#中途拔usb线就panic" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;我有印象！大概率是驱动释放太快、内存释放出了问题！Pick: &lt;a href="https://github.com/torvalds/linux/commit/b81ac4395bbeaf36e078dea1a48c02dd97b76235"&gt;usb: gadget: uvc: allow for application to cleanly shutdown&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="怎么没输出了危机来临"&gt;怎么没输出了？危机来临！&lt;a class="headerlink" href="#怎么没输出了危机来临" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;从有到无了，怎么办？Bisect！不过由于中间commit很乱，所以遇到了不少需要调整commit顺序保证编译通过的状况。&lt;/p&gt;
&lt;p&gt;谁竟想结果是scatter gather多线程性能优化完全用不了。用不了用不了，用不了就扬了！总体尝试了四条路线：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;维持原状（OK）&lt;/li&gt;
&lt;li&gt;在没有sg的基础上合并新功能（OK，极麻烦，难解耦）&lt;ul&gt;
&lt;li&gt;甚至让我整出了protocol error&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在既有UVC1.5的基础上实现VIDIOC_ENUM_*（NG，后面甚至带来modem crash，没有panic）&lt;/li&gt;
&lt;li&gt;直接在最新状况下用hack的方式强制走普通模式（OK，参考yaap得来的结果）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这编译内核真的累啊，不知道为什么每次编译要固定16分钟，像是没有增量编译，测试起来非常考验人心态。&lt;/p&gt;
&lt;h4 id="偶尔用一段时间后会panic"&gt;偶尔用一段时间后会panic？&lt;a class="headerlink" href="#偶尔用一段时间后会panic" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;要是有日志看就好了……等等，android有pstore！panic日志可以在 &lt;code&gt;/sys/fs/pstore/*&lt;/code&gt; 找到，发现了下面的内容：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;[  903.307954] c5   1126 SMP: failed to stop secondary CPUs 0-7
[  903.908145] c5   1126 set_restart_msg: set restart msg = `KP: HwBinder:1113_1 PC:__pi_strcmp+0x88/0x154 LR:uvc_alloc+0x1f4/0x3b4'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;uvc_alloc？emm，搜索后发现是自己漏了一个fix。之前提到的实现VIDIOC必须功能的commit引入了问题，修复用的patch是&lt;a href="https://github.com/torvalds/linux/commit/3180d827c807d8d6e5d6ba4f2e08eed9efa083af"&gt;usb: gadget: uvc: don’t put item still in use&lt;/a&gt;。&lt;/p&gt;
&lt;h4 id="感叹内核折磨人"&gt;感叹：内核折磨人&lt;a class="headerlink" href="#感叹内核折磨人" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;内核折磨人啊(´；ω ；｀)&lt;/p&gt;
&lt;h3 id="来看看输出哎不是红蓝颜色通道怎么反了啊"&gt;来看看输出～哎不是，红蓝颜色通道怎么反了啊！&lt;a class="headerlink" href="#来看看输出哎不是红蓝颜色通道怎么反了啊" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;好不容易有了信号，想照照人、却成了Avatar？而且还只在上位机、预览没事？&lt;/p&gt;
&lt;p&gt;就是那种，把OpenCV BGR当RGB处理的感觉。幸运的是包括YAAP的维护者在内有几位开发者&lt;a href="https://github.com/yaap/packages_services_DeviceAsWebcam/commit/fbcc1c2f3af65fcd411cd9abc8873cb5f497c6a5"&gt;已经摸清了问题点&lt;/a&gt;：高通设备上用硬件加速编码会带来颜色问题，包括RB通道翻转。&lt;/p&gt;
&lt;p&gt;恐怕DeviceAsWebcam这个程序就只是为了Tensor平台……&lt;/p&gt;
&lt;h3 id="obs上的小问题"&gt;OBS上的小问题&lt;a class="headerlink" href="#obs上的小问题" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;通常用QV4L2来测试摄像头应该是够用。分辨率、编码均能正常调整，就是帧率好像没有真的变过。&lt;/p&gt;
&lt;p&gt;但是在OBS这边，每次修改摄像头配置后，摄像头输出都会卡住，需要重载UVC功能/重插USB线，尽管手机上的预览还是没有问题。&lt;/p&gt;
&lt;p&gt;暂时找不到问题根源。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;a class="headerlink" href="#总结" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;改kernel config、打开功能&lt;/li&gt;
&lt;li&gt;修uvc gadget驱动、补全API&lt;/li&gt;
&lt;li&gt;hack HIDL、强兼UVC&lt;/li&gt;
&lt;li&gt;编init脚本、初始化节点&lt;/li&gt;
&lt;li&gt;改DeviceAsWebcam服务、适配qcom平台&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;学到了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;git log -G&lt;/li&gt;
&lt;li&gt;git rebase –update-refs&lt;/li&gt;
&lt;li&gt;git workspace&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;又及：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cherry pick时的信条：相信开发者（实际WTF）&lt;/li&gt;
&lt;li&gt;&lt;del&gt;什么叫垂直整合、全栈开发啊&lt;/del&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;( _ _ )&amp;gt;&amp;lt;(=- . -=)&amp;gt;&amp;lt;(=~ O ~=)&amp;gt;……&amp;lt;(=- . -=)&amp;gt;&lt;/p&gt;
&lt;p&gt;累了。纯发泄。&lt;/p&gt;</content><category term="Misc"/><category term="android"/><category term="usb-gadget"/></entry><entry><title>podman使用及与systemd集成</title><link href="https://techie-s.work/posts/2024/12/podman-practice-and-systemd-integration/" rel="alternate"/><published>2024-12-29T11:27:07+08:00</published><updated>2024-12-29T11:27:07+08:00</updated><author><name/></author><id>tag:techie-s.work,2024-12-29:/posts/2024/12/podman-practice-and-systemd-integration/</id><summary type="html">&lt;p&gt;曾经不喜欢docker而将podman视为替代。脱离docker依赖后又渐渐懒得用podman了。如今因为偷懒重拾podman，发现它成长超乎我想象，非常符合我现在部署各种服务的需求。&lt;/p&gt;&lt;p&gt;这里简单记录 …&lt;/p&gt;</summary><content type="html">
&lt;p&gt;曾经不喜欢docker而将podman视为替代。脱离docker依赖后又渐渐懒得用podman了。如今因为偷懒重拾podman，发现它成长超乎我想象，非常符合我现在部署各种服务的需求。&lt;/p&gt;
&lt;p&gt;这里简单记录一下。&lt;/p&gt;
&lt;h2 id="开端"&gt;开端&lt;a class="headerlink" href="#开端" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;部署Perplexica，实在不想往虚拟机环境里塞更多难以管理的环境了。项目提供的是docker，而我又不喜欢docker，于是重拾Podman，顺带学习一下。&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;不喜欢docker哪些点？&lt;/p&gt;
&lt;p&gt;其实就一点：深度依赖守护进程。这引发了下列问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;临时修改配置很繁琐&lt;/li&gt;
&lt;li&gt;还是个root级别服务，不够安全&lt;/li&gt;
&lt;li&gt;重量级程序&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="懒到极致"&gt;懒到极致&lt;a class="headerlink" href="#懒到极致" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="podman-docker"&gt;podman-docker&lt;a class="headerlink" href="#podman-docker" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;太懒了，直接照抄docker的命令！用到podman-docker直接包裹podman、试图兼容docker命令行。目前来看podman发展下来并不想完全兼容docker命令行，不过多数命令的格式仍然相同。&lt;/p&gt;
&lt;p&gt;但是选择这种非原生操作仍然会引入不确定因素。&lt;/p&gt;
&lt;h3 id="podman-compose"&gt;podman-compose&lt;a class="headerlink" href="#podman-compose" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;podman-compose是第三方为podman编写的docker-compose。虽然和docker-compose有不少差异，但是主要功能类似。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;podman-compose up -d&lt;/code&gt;，哎，服务就起来了。但是这里暗藏玄机，在后面会一一显现。&lt;/p&gt;
&lt;h2 id="systemd集成"&gt;systemd集成&lt;a class="headerlink" href="#systemd集成" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="手搓systemd配置用podman-compose启动服务发现问题"&gt;手搓systemd配置用podman compose启动服务发现问题&lt;a class="headerlink" href="#手搓systemd配置用podman-compose启动服务发现问题" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;先说结果：和我想的不太一样，不能用老方法。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不能用系统服务配置以非root身份开启容器，会遇到一些crun运行环境问题。&lt;/li&gt;
&lt;li&gt;即便解决了前述问题，crun仍会fallback cgroupfs（因为systemd）。&lt;/li&gt;
&lt;li&gt;这也不是大问题，但是容器启动后service会挂掉，类似于用户登出带走了所有程序。&lt;/li&gt;
&lt;li&gt;情况类似，就算登陆用户后手动直接启动的容器也会在登出后被停止。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我就不想把折腾再写一遍了 |ω・`）&lt;/p&gt;
&lt;p&gt;这里就引出了几个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;以用户身份启动容器需要什么条件？&lt;/li&gt;
&lt;li&gt;如何用systemd以用户身份启动容器？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在尝试过程中，发现日志里有提示：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;The cgroupv2 manager is set to systemd but there is no systemd user session available
For using systemd, you may need to log in using a user session
Alternatively, you can enable lingering with: `loginctl enable-linger 1000` (possibly as root)
Falling back to --cgroup-manager=cgroupfs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;看起来像是要维护一个session。linger又是什么？这个问题晚点再答。&lt;/p&gt;
&lt;h3 id="尝试podlet失败"&gt;尝试podlet（失败）&lt;a class="headerlink" href="#尝试podlet失败" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;赶时间，所以在网上直接找podman、systemd集成的相关文章。其中就有讲到podlet。&lt;/p&gt;
&lt;p&gt;因为compose配置文件不能完美兼容、现有pod使用了尚未支持的参数，podlet无法生成合适的服务配置。因而无法使用podlet。暂时搁置。&lt;/p&gt;
&lt;p&gt;不过如果启用单个容器还是可以看看podlet的。&lt;/p&gt;
&lt;h3 id="遵循podman-compose提示"&gt;遵循podman-compose提示&lt;a class="headerlink" href="#遵循podman-compose提示" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在折腾途中，发现podman-compose有一个systemd指令，直接在perplexica的源码文件夹里执行看看：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Perplexica bash&amp;gt; podman-compose systemd      

you can use systemd commands like enable, start, stop, status, cat
all without `sudo` like this:

        systemctl --user enable --now 'podman-compose@perplexica'
        systemctl --user status 'podman-compose@perplexica'
        journalctl --user -xeu 'podman-compose@perplexica'

and for that to work outside a session
you might need to run the following command *once*

        sudo loginctl enable-linger 'user'

you can use podman commands like:

        podman pod ps
        podman pod stats 'pod_perplexica'
        podman pod logs --tail=10 -f 'pod_perplexica'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;有不少有趣的东西哎：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;一个compose相当于k8s里面的pod，这点我之前完全忽略了（生疏了）。&lt;/li&gt;
&lt;li&gt;随系统启动的服务可以只用某个用户自己配置的服务。前提是 &lt;code&gt;enable-linger&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;按理从文档看应该要逐渐deprecate podman-compose，但是目前看来还是很好用的嘛！&lt;/p&gt;
&lt;/div&gt;
&lt;h4 id="systemd-user-service-简述"&gt;systemd user service 简述&lt;a class="headerlink" href="#systemd-user-service-简述" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;由systemd管理的Linux系统为每个用户提供了管理自己服务的方法，user instance。通过user instance，用户可以自行管理自己的user service。用户自定义的服务配置文件通常位于 &lt;code&gt;$XDG_CONFIG_PATH/systemd/user&lt;/code&gt;，暂且忽略由系统提供的用户服务模板。&lt;/p&gt;
&lt;p&gt;每次用户登陆（创建新会话）时，会创建一个systemd user instance并启动用户激活的服务。给几个现实中还算常见的例子：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单用户下的pipewire守护进程&lt;/li&gt;
&lt;li&gt;gpg-agent&lt;/li&gt;
&lt;li&gt;appimagelauncherd&lt;/li&gt;
&lt;li&gt;代理服务，随用户登陆启动&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以读 &lt;a href="https://wiki.archlinux.org/title/Systemd/User"&gt;ArchWiki&lt;/a&gt; 了解更多。&lt;/p&gt;
&lt;h5 id="linger"&gt;linger？&lt;a class="headerlink" href="#linger" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;通常情况下，用户服务只会在用户创建首个活跃会话（也就是登陆）时自动启动，并在用户登出全部会话后自动停止。&lt;/p&gt;
&lt;p&gt;这挺智能的不是吗？但是对于想使用专用非root用户、借由podman-compose启动容器的我来说是个问题。可能出于类似的原因，systemd自233版本后提供了 &lt;code&gt;loginctl enable-linger&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;用户被标记linger后，其用户服务会随系统启动一并启动，并且不会因为用户登出而停止。&lt;/p&gt;
&lt;h4 id="podman-compose与systemd集成"&gt;podman-compose与systemd集成&lt;a class="headerlink" href="#podman-compose与systemd集成" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;需要先装个服务模板，根据提示，以root用户执行执行下面一条命令：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;podman-compose&lt;span class="w"&gt; &lt;/span&gt;systemd&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;create-unit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;服务模板会写至 &lt;code&gt;/etc/systemd/user/podman-compose@.service&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;接着进一步根据提示，注册所需要的compose服务。在docker-compose.yaml所在位置执行命令注册服务（配置会被写入 &lt;code&gt;$XDG_CONFIG_PATH/containers/compose/projects&lt;/code&gt;）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;podman-compose&lt;span class="w"&gt; &lt;/span&gt;systemd&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;register
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最后激活并启动服务，标记用户为linger&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 注意名称替换&lt;/span&gt;
systemctl&lt;span class="w"&gt; &lt;/span&gt;--user&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--now&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'podman-compose@perplexica'&lt;/span&gt;
&lt;span class="c1"&gt;# 下面的指令只需执行一次&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;loginctl&lt;span class="w"&gt; &lt;/span&gt;enable-linger&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;之后可以重启机器验证一下容器是否正常启动。&lt;/p&gt;
&lt;h2 id="探寻"&gt;探寻&lt;a class="headerlink" href="#探寻" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="podlet-quadlet"&gt;podlet &amp;amp; quadlet&lt;a class="headerlink" href="#podlet-quadlet" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;看起来使用quadlet是大势所趋，就想看看它到底是什么。&lt;/p&gt;
&lt;p&gt;rootless是podman精华的一部分。但是podman没有daemon！它需要一个daemon维护容器启停，aka orchestrator。而这时的orchestrator可以是systemd，毕竟它已经管了一堆服务了。&lt;/p&gt;
&lt;p&gt;systemd支持&lt;a href="https://www.freedesktop.org/software/systemd/man/systemd.generator.html"&gt;systemd-generator&lt;/a&gt;来更灵活地从其它配置文件生成systemd的服务配置。安装podman会同时安装一个generator配置，其会调用 &lt;code&gt;quadlet&lt;/code&gt; 生成正确的systemd服务配置。quadlet并不在通常的可执行文件位置（&lt;code&gt;*/bin&lt;/code&gt;之类），用户通常无需手动运行它。对于podman quadlet，generator会将quadlet配置转换为合适的systemd服务配置。&lt;/p&gt;
&lt;p&gt;那podlet是干什么的？简单说它可以从现有的podman命令、compose文件、container或pod生成&lt;a href="https://docs.podman.io/en/stable/markdown/podman-systemd.unit.5.html"&gt;quadlet配置文件&lt;/a&gt;（&lt;code&gt;*.container&lt;/code&gt; 文件），丢进合适的位置重载后就能用啦。不过看起来podlet功能不甚完善，至少在compose场景下有很多不兼容之处。&lt;/p&gt;
&lt;h2 id="一些小问题"&gt;一些小问题&lt;a class="headerlink" href="#一些小问题" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="容器连接宿主"&gt;容器连接宿主&lt;a class="headerlink" href="#容器连接宿主" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;对于docker，有个 &lt;code&gt;host.docker.internal&lt;/code&gt; 用来连接宿主机。只不过需要配置一下 &lt;code&gt;extra_hosts&lt;/code&gt;，增加 &lt;code&gt;'host.docker.internal:host-gateway'&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;与docker类似，podman提供了 &lt;code&gt;host.containers.internal&lt;/code&gt; 来连接宿主，而且不需要任何额外配置。迁移时需要注意。&lt;/p&gt;
&lt;p&gt;如果服务认hostname，那还要格外注意呀！&lt;/p&gt;
&lt;h2 id="结语"&gt;结语&lt;a class="headerlink" href="#结语" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;现在我部署服务大多在母机上开容器再手动配置环境、改写systemd配置文件，偶尔在必要的时候开个虚拟机。既然podman可以轻松与systemd集成，那未来向podman容器迁移也不错？用起来比nix简单不少，也有一定的reproducibility。&lt;/p&gt;
&lt;p&gt;k8s？一个人用的私有云要什么k8s啊增加心智负担 (ノ=Д=)ノ┻━┻&lt;/p&gt;</content><category term="Homelab"/><category term="podman"/><category term="systemd"/><category term="container"/><category term="linux"/><category term="devops"/></entry><entry><title>用FreeCAD做刚体和流体的物理仿真</title><link href="https://techie-s.work/posts/2024/06/freecad-physics-simulation/" rel="alternate"/><published>2024-06-08T12:23:08+08:00</published><updated>2024-06-11T20:23:24+08:00</updated><author><name/></author><id>tag:techie-s.work,2024-06-08:/posts/2024/06/freecad-physics-simulation/</id><summary type="html">&lt;p&gt;&lt;img alt="freecad-fem-and-cfd" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/banner.svg"/&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;太长不读版：把FreeCAD插件CfdOF用到的软件用Nix打包好，得到可靠的软件环境后，分别做了亚克力弹性组件和导风漏斗的物理仿真。&lt;/strong&gt;&lt;/p&gt;</summary><content type="html">
&lt;p&gt;&lt;img alt="freecad-fem-and-cfd" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/banner.svg"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;太长不读版：把FreeCAD插件CfdOF用到的软件用Nix打包好，得到可靠的软件环境后，分别做了亚克力弹性组件和导风漏斗的物理仿真。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;大量篇幅给了nix软件打包，想读物理仿真实战可以直接从目录跳过去。&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="一切的起源自制导风漏斗反射了风扇气流"&gt;一切的起源：自制导风漏斗反射了风扇气流&lt;a class="headerlink" href="#一切的起源自制导风漏斗反射了风扇气流" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;想用12cm机箱风扇做一个排风系统，直接用FreeCAD凭感觉建了个导风漏斗模型，但没想到用3D打印的实物测试发现，风扇的气流几乎都从扇叶末端的缝隙中反射出来了，变得很诡异。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;FreeCAD作为一款强大的参数建模工具，。据闻有一些公司在基于FreeCAD做二次开发。听从业网友说，“SolidWorks能做的FreeCAD也能做，做不了的都不能做，平时工作用UG多，FreeCAD缺点在运行卡顿”。这个软件也很自然地成为了以ArchLinux为主力操作系统的我所使用的机械设计软件。&lt;/p&gt;
&lt;p&gt;我以前用FreeCAD做过亚克力模型的刚体仿真，自然而然就会想怎样用FreeCAD做流体仿真。实际上我几乎没有流体力学相关知识，想先把软件环境搭建起来，之后应该船到桥头自然直吧！&lt;/p&gt;
&lt;h2 id="首先是失败的工作环境配置尝试"&gt;首先是失败的工作环境配置尝试！&lt;a class="headerlink" href="#首先是失败的工作环境配置尝试" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我使用的是ArchLinux。FreeCAD在官方软件源里有，直接可以装，CfdOF工作台也可以通过Addon-Manager安装，所以比较麻烦的地方是安装CfdOF依赖的流体计算软件。&lt;/p&gt;
&lt;p&gt;CfdOF依赖的流体分析相关软件包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OpenFOAM，流体动力学计算软件&lt;/li&gt;
&lt;li&gt;cfmesh，算是OpenFOAM的附件，CfdOF维护者（oliveroxtoby）提供了一份能配合最新OpenFOAM编译的版本&lt;/li&gt;
&lt;li&gt;hisa，也算是OpenFOAM的附件，源码貌似也源自CfdOF维护者（oliveroxtoby）另外提供的渠道，但是改没改过不知道&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="初次尝试直接在arch上安装openfoam-aur包"&gt;初次尝试：直接在Arch上安装OpenFOAM AUR包&lt;a class="headerlink" href="#初次尝试直接在arch上安装openfoam-aur包" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;作为忠实的Arch用户，我一定会去看AUR的。不过由于见到过AUR包捆绑了一堆“垃圾”的案例，我还是对AUR包进行了审核。&lt;/p&gt;
&lt;p&gt;一看有不少OpenFOAM的用户软件包：&lt;code&gt;openfoam-org&lt;/code&gt;、&lt;code&gt;openfoam-com&lt;/code&gt;、&lt;code&gt;openfoam-com-precice&lt;/code&gt;、&lt;code&gt;openfoam-com-git&lt;/code&gt;、&lt;code&gt;openfoam-9-org&lt;/code&gt;。
怎么办呢，直接看最近更新过的把，想办法装新的。&lt;/p&gt;
&lt;p&gt;去看&lt;code&gt;openfoam-org&lt;/code&gt;的评论区，看到有人报编译问题，尚无明确解答。此外看到软件维护者置顶了说，在&lt;code&gt;arch4edu&lt;/code&gt;仓库（类似&lt;code&gt;archlinuxcn&lt;/code&gt;）有预编译版本。再一看软件编译依赖还有其它的AUR包，我有些不愿意自己编译。&lt;/p&gt;
&lt;p&gt;我知道能用&lt;code&gt;yay&lt;/code&gt;、&lt;code&gt;paru&lt;/code&gt;之类的装，但是看了下OpenFOAM官方仓库编译说明里写的1小时编译时间，我选择暂时搁置这一路径。&lt;/p&gt;
&lt;h3 id="尝试偷懒希望能直接用arch4edu仓库编译好的软件包"&gt;尝试偷懒：希望能直接用&lt;code&gt;arch4edu&lt;/code&gt;仓库编译好的软件包&lt;a class="headerlink" href="#尝试偷懒希望能直接用arch4edu仓库编译好的软件包" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;参考&lt;a href="https://github.com/arch4edu/arch4edu/wiki/Add-arch4edu-to-your-Archlinux"&gt;arch4edu的官方说明&lt;/a&gt;，我给自己的Arch加上了arch4edu仓库。随后我直接就装上了&lt;code&gt;openfoam-org&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;走这步需要在终端开FreeCAD，因为OpenFOAM套件环境要source一个脚本加载。软件包也在&lt;code&gt;/etc/profile.d&lt;/code&gt;加了那个脚本，但我暂时不想重新登陆。先试试，按下面列出的指令操作：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/profile.d/openfoam-11.sh
ofoam&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 这是个加载环境的指令，实际是 source /opt/OpenFOAM/OpenFOAM-11/etc/bashrc&lt;/span&gt;
freecad
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;暂时不管加载环境时可能的报错（后面发现也不用管），加载CfdOF工作台后，进入preferences，左侧栏选择CfdOF，在其中找到&lt;code&gt;Run dependency checker&lt;/code&gt;的按钮，点击检查运行环境。告诉我这个：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Checking CFD workbench dependencies...
Checking FreeCAD version
FreeCAD version: 0.21
Checking for OpenFOAM:
System: Linux
Runtime: Posix
OpenFOAM directory: /opt/OpenFOAM/OpenFOAM-11
Running  echo $WM_PROJECT_VERSION
Executing: echo $WM_PROJECT_VERSION
11
OpenFOAM installation found, but unable to run command: 'PySide6.QtCore.QProcess' object has no attribute 'Timedout'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;命令行显示的错误：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/home/user/.local/share/FreeCAD/Mod/CfdOF/./CfdOF/CfdConsoleProcess.py"&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;153&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;waitForFinished&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;.&lt;span class="nv"&gt;process&lt;/span&gt;.&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;self&lt;/span&gt;.&lt;span class="nv"&gt;process&lt;/span&gt;.&lt;span class="nv"&gt;Timedout&lt;/span&gt;:
&lt;span class="w"&gt;                               &lt;/span&gt;&lt;span class="o"&gt;^^^^^^^^^^^^^^^^^^^^^&lt;/span&gt;
&lt;span class="nv"&gt;AttributeError&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'PySide6.QtCore.QProcess'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;attribute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Timedout'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Why? 搞不明白。怎么会有个Python+QT的报错？总之这看上去很不妙啊……这条路大概率行不通，或者走到最后会很麻烦(´_&amp;gt;`)。&lt;/p&gt;
&lt;p&gt;果断把相关包删了，把arch4edu移除，也不想试别的了。因为我知道有一种方法能更稳定地复现环境——&lt;code&gt;nix&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="完美可复现的开发环境使用nix-shell"&gt;完美可复现的开发环境：使用nix-shell&lt;a class="headerlink" href="#完美可复现的开发环境使用nix-shell" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;这一步也一波三折，但还是成功了！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;同类项目&lt;/p&gt;
&lt;p&gt;当我完成这个项目后，我才发现有人做了类似的工作，且还在维护。&lt;a href="https://github.com/precice/nix-packages"&gt;preCICE adapters and solvers packaged with the Nix package manager&lt;/a&gt;，感兴趣可以一看。不过它用了Nix的实验性功能flakes（这功能长久没稳定下来），可能没法直接给nix-shell用（不过可以看看&lt;code&gt;nix shell&lt;/code&gt;）。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;我有一段时间用过NixOS，它的完全可复现性给我留下了深刻印象。不过我还是用不惯NixOS，在临时要改一些细小配置时显得太麻烦。&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;本节内容只是过程记录&lt;/p&gt;
&lt;p&gt;想使直接使用完成的环境，请看文末&lt;a href="#总结与补充说明"&gt;总结与补充说明&lt;/a&gt;提到的git仓库。本节存在一定错误尝试记录，切勿盲从（除非想一起踩一遍坑）。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;nix-shell &amp;amp; nix-env&lt;/p&gt;
&lt;p&gt;貌似nix-shell、nix-env二者有不少相似之处，只不过后者是持久化的。后续感兴趣可以研究下。&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="基本的nix-shell环境"&gt;基本的nix-shell环境&lt;a class="headerlink" href="#基本的nix-shell环境" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;官方教程&lt;a href="https://nix.dev/tutorials/first-steps/declarative-shell.html"&gt;用shell.nix的实现声明式shell环境&lt;/a&gt;写得不错，比直接在搜索引擎里搜&lt;code&gt;nix-shell tutorial&lt;/code&gt;找到的东西靠谱。正式写配置前最好再通读一遍&lt;a href="https://nix.dev/manual/nix/2.22/language/"&gt;nix基本语法&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;由于搜索引擎的搜索质量不佳，我一开始这样写nix：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# freecad-cfd.nix&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; pkgs &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;import&lt;/span&gt; &lt;span class="l"&gt;&amp;lt;nixpkgs&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
pkgs&lt;span class="o"&gt;.&lt;/span&gt;mkShell &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# nativeBuildInputs is usually what you want -- tools you need to run&lt;/span&gt;
    &lt;span class="ss"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; pkgs&lt;span class="o"&gt;.&lt;/span&gt;freecad &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但我想，这样还得在系统里配置nix-channel，不算完全固定的配置，于是根据前面提到的教程改成了下面这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# freecad-cfd.nix&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt;
  &lt;span class="ss"&gt;nixpkgs_ball&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; fetchTarball &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/NixOS/nixpkgs/tarball/nixos-24.05"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1q7y5ygr805l5axcjhn0rn3wj8zrwbrr0c6a8xd981zh8iccmx0p"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="ss"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;import&lt;/span&gt; nixpkgs_ball &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;allowUnfree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;in&lt;/span&gt;
pkgs&lt;span class="o"&gt;.&lt;/span&gt;mkShell &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# nativeBuildInputs is usually what you want -- tools you need to run&lt;/span&gt;
    &lt;span class="ss"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; pkgs&lt;span class="o"&gt;.&lt;/span&gt;freecad &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;区别在于，前者需要输入参数，后者不接受参数并自行定义了用到的数据（从网络获取，并锁定了内容的哈希值）。&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;如何获取内容哈希值&lt;/p&gt;
&lt;p&gt;nix计算哈希的方法和命令行工具（sha256sum）不同。对于&lt;code&gt;fetchTarball&lt;/code&gt;这种会自动解压的操作，可以使用&lt;code&gt;nix-prefetch-url --unpack $url&lt;/code&gt;命令来获取内容哈希值。当然也可以直接写个错的哈希值，看错误报告写的实际哈希值是什么（可能会以&lt;code&gt;哈希类型-&lt;/code&gt;开头，比如&lt;code&gt;sha256-&lt;/code&gt;，可以一起复制），再添进配置。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;同理把nixgl也在下面加上，这个我也是读了前述教程才明白怎么配置。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# freecad-cfd.nix&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt;
  &lt;span class="ss"&gt;nixpkgs_ball&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; fetchTarball &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/NixOS/nixpkgs/tarball/nixos-24.05"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1q7y5ygr805l5axcjhn0rn3wj8zrwbrr0c6a8xd981zh8iccmx0p"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="ss"&gt;nixgl_ball&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; fetchTarball &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/nix-community/nixGL/tarball/310f8e49a149e4c9ea52f1adf70cdc768ec53f8a"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1crnbv3mdx83xjwl2j63rwwl9qfgi2f1lr53zzjlby5lh50xjz4n"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="ss"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;import&lt;/span&gt; nixpkgs_ball &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;allowUnfree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="ss"&gt;nixgl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;import&lt;/span&gt; nixgl_ball &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="k"&gt;in&lt;/span&gt;
pkgs&lt;span class="o"&gt;.&lt;/span&gt;mkShell &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# nativeBuildInputs is usually what you want -- tools you need to run&lt;/span&gt;
    &lt;span class="ss"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        pkgs&lt;span class="o"&gt;.&lt;/span&gt;freecad
        nixgl&lt;span class="o"&gt;.&lt;/span&gt;nixGLIntel &lt;span class="c1"&gt;# Mesa OpenGL implementation (intel, amd, nouveau, ...).&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="c1"&gt;# 看到nixgl仓库有人开issue提到了这个，nix-shell workaround&lt;/span&gt;
    &lt;span class="ss"&gt;shellHook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;''&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        export LD_LIBRARY_PATH=$(nixGLIntel printenv LD_LIBRARY_PATH):$LD_LIBRARY_PATH&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;        ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;nixGL wrapper的选择&lt;/p&gt;
&lt;p&gt;可以读一下&lt;a href="https://github.com/nix-community/nixGL?tab=readme-ov-file#nix-channel-recommended"&gt;nixGL官方仓库的介绍&lt;/a&gt;，选用合适自己的wrapper。此处所用的是适用于Intel显卡、AMD显卡、或Nouveau驱动下NVIDIA显卡的wrapper。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;用下面的指令构建并进入所配置的shell环境，运行FreeCAD，如果FreeCAD正常运行就算小功告成！&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 运行后会构建并打开一个shell环境&lt;/span&gt;
nix-shell&lt;span class="w"&gt; &lt;/span&gt;freecad-cfd.nix
&lt;span class="c1"&gt;# 要让图形系统正常工作，需要套个nixgl&lt;/span&gt;
nixGLIntel&lt;span class="w"&gt; &lt;/span&gt;freecad
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="打包openfoam"&gt;打包OpenFOAM&lt;a class="headerlink" href="#打包openfoam" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;看了OpenFOAM官方的编译说明，挺简洁的，但是没写编译环境依赖。于是我参考两个AUR包（主要是&lt;code&gt;openfoam-com&lt;/code&gt;）、在nix这边复（照）现（抄）了一遍编译流程。除了&lt;code&gt;kahip&lt;/code&gt;、&lt;code&gt;adios&lt;/code&gt;之类的辅助工具尚没有在nixpkgs里的包，其它基本都有。由于nix包的位置不固定，需要多写一小段shell脚本打印openfoam的环境位置，方便其它程序使用。我直接在nixpkgs里找包来抄&lt;code&gt;stdenv.mkDerivation&lt;/code&gt;的用法。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# openfoam/default.nix&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; lib&lt;span class="p"&gt;,&lt;/span&gt; stdenv
&lt;span class="p"&gt;,&lt;/span&gt; cmake
&lt;span class="p"&gt;,&lt;/span&gt; boost
&lt;span class="p"&gt;,&lt;/span&gt; bzip2
&lt;span class="p"&gt;,&lt;/span&gt; cgal
&lt;span class="p"&gt;,&lt;/span&gt; fftw
&lt;span class="p"&gt;,&lt;/span&gt; flex
&lt;span class="p"&gt;,&lt;/span&gt; openmpi
&lt;span class="p"&gt;,&lt;/span&gt; paraview
&lt;span class="p"&gt;,&lt;/span&gt; parmetis
&lt;span class="p"&gt;,&lt;/span&gt; scotch
&lt;span class="p"&gt;,&lt;/span&gt; zlib
&lt;span class="p"&gt;}:&lt;/span&gt;

stdenv&lt;span class="o"&gt;.&lt;/span&gt;mkDerivation &lt;span class="k"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;pname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"openfoam"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="ss"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2312"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="ss"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; fetchTarball &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://develop.openfoam.com/Development/openfoam/-/archive/OpenFOAM-v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;version&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/openfoam-OpenFOAM-v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;version&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.tar.gz"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="ss"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; boost cgal paraview parmetis scotch openmpi fftw zlib &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="ss"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; cmake bzip2 flex &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="ss"&gt;configurePhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;''&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    echo "# Preferences for arch-linux&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    export WM_COMPILER_TYPE=system&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    export WM_MPLIB=SYSTEMOPENMPI&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    # End" \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    &amp;gt; etc/prefs.sh&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;    ./bin/tools/foamConfigurePaths \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      -adios adios-system \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      -boost boost-system \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      -cgal cgal-system \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      -fftw fftw-system \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      -metis metis-system \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      -paraview paraview-system \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      -scotch scotch-system \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      ;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="ss"&gt;buildPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;''&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    export FOAM_CONFIG_MODE="o"&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    unset FOAM_SETTINGS&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    source ./etc/bashrc || echo "Ignore spurious sourcing error"&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    ./Allwmake -j&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    wclean all&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    wmakeLnIncludeAll&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="ss"&gt;installPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;''&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    install -d $out/opt/OpenFOAM/ThirdParty-v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;version&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt; $out/etc/profile.d&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    cp -r $(pwd) $out/opt/OpenFOAM/OpenFOAM-v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;version&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    chmod -R 755 $out/opt/OpenFOAM/OpenFOAM-v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;version&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/bin&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    chmod 755 $out/opt/OpenFOAM/OpenFOAM-v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;version&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/etc/*&lt;/span&gt;

&lt;span class="s s-Multiline"&gt;    install -d $out/bin&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    echo 'echo $(readlink -f "$(dirname $0)/../opt/OpenFOAM/OpenFOAM-v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;version&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;")' | \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      install -Dm755 /dev/stdin  $out/bin/openfoam-home.sh&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="ss"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; lib&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The free, open source CFD software developed primarily by OpenCFD Ltd since 2004"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;homepage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://www.openfoam.com/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;license&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; licenses&lt;span class="o"&gt;.&lt;/span&gt;gpl3&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;platforms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; platforms&lt;span class="o"&gt;.&lt;/span&gt;all&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后把这个包加到freecad-cfd.nix里：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# freecad-cfd.nix&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="ss"&gt;openfoam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; pkgs&lt;span class="o"&gt;.&lt;/span&gt;callPackage &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;import&lt;/span&gt; &lt;span class="l"&gt;./openfoam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;in&lt;/span&gt;
pkgs&lt;span class="o"&gt;.&lt;/span&gt;mkShell &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    openfoam
    &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在中间buildPhase遇到了脚本明明存在却无法执行的问题，提示&lt;code&gt;required file not found&lt;/code&gt;，感觉很奇怪，打开对应文件一看：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;#！ /bin/sh&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&amp;lt;(=╯▽╰=)&amp;gt;原来是shebang的问题……要时刻记住nix不遵守FHS，可我该怎么修呢？好消息是nix环境下有提供patch shebang的工具：&lt;code&gt;patchShebangs&lt;/code&gt;，它能自动递归地修补所有检测到的shebang。本着最小化修改的原则，我找出了必须修改的脚本，在configurePhase的开头加上这些修改流程：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# openfoam/default.nix&lt;/span&gt;
&lt;span class="ss"&gt;configurePhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;''&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;  patchShebangs wmake/w*&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;  patchShebangs wmake/scripts&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;  patchShebangs Allwmake&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;  ...&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;  ''&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;编译完后运行CfdOF的依赖检测，告诉我尚不支持OpenFOAM 2312版本。我在此时才注意到了CfdOF还依赖另外两个工具&lt;code&gt;cfmesh&lt;/code&gt;和&lt;code&gt;hisa&lt;/code&gt;（此前都没察觉到）。&lt;/p&gt;
&lt;p&gt;仔细看了看CfdOF的说明文档：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Prerequisites:
&lt;span class="k"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;OpenFOAM Foundation versions 9-10 or ESI-OpenCFD versions 2006-2306
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;顺便看看arch4edu里的openfoam软件包的版本：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;arch4edu&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;openfoam&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v2312&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CFD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;toolbox&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;openfoam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;arch4edu&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;openfoam&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;11.20240116&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CFD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;toolbox&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;openfoam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这也不对！那也就更没必要回去看AUR包的方法了。Arch这点是这样，软件只有最新，有点烦。&lt;/p&gt;
&lt;p&gt;我需要降级！还好此时的降级只是把版本号、源码包、源码包的hash调整一下。借机还可以把版本号和hash参数化：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# openfoam/default.nix&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt; version &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"2306"&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt; hash &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"1z0sna5jxlyfz8s7vi28m47iwjjjbzg9ycz5maz2gymchg7lw6v7"&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt;
stdenv&lt;span class="o"&gt;.&lt;/span&gt;mkDerivation &lt;span class="k"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;pname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"openfoam"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;inherit&lt;/span&gt; version&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="ss"&gt;foam_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; hash&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="ss"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; fetchTarball &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://develop.openfoam.com/Development/openfoam/-/archive/OpenFOAM-v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;version&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/openfoam-OpenFOAM-v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;version&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.tar.gz"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="ss"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; foam_hash&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;OpenFOAM的部分就差不多搞定了。最后在shellHook里加上自动加载OpenFOAM配置的代码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# freecad-cfd.nix&lt;/span&gt;
&lt;span class="ss"&gt;shellHook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;''&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;  ...&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;  source &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;openfoam&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/opt/OpenFOAM/OpenFOAM-v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;openfoam&lt;span class="o"&gt;.&lt;/span&gt;version&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/etc/bashrc || true&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;  ...&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;  ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="更多修补"&gt;更多修补&lt;a class="headerlink" href="#更多修补" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;到这里本来都编译正常，但是后面跑CfdOF例程时出了状况：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Decomposing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mesh&lt;/span&gt;

&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mesh&lt;/span&gt;

&lt;span class="n"&gt;Calculating&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;distribution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cells&lt;/span&gt;
&lt;span class="n"&gt;Decomposition&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scotch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;region0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Selecting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;decompositionConstraint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;preserveBaffles&lt;/span&gt;
&lt;span class="n"&gt;preserveBaffles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;constraints&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;preserve&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;baffles&lt;/span&gt;


&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FOAM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FATAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openfoam&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2306&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Attempted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;scotch&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;without&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scotchDecomp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scotchDecomp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;instead&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;Please&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;scotch&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ensure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;libscotch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;so&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LD_LIBRARY_PATH&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scotchDecomp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;built&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;parallel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;decompose&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;scotchDecomp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Dynamically&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linking&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;scotch&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;decomposition&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;


&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;virtual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Foam&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;labelList&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Foam&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;scotchDecomp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;decompose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Foam&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;polyMesh&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Foam&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;labelList&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Foam&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;pointField&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Foam&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;scalarField&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dummyScotchDecomp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;111.&lt;/span&gt;

&lt;span class="n"&gt;FOAM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exiting&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;缺少&lt;code&gt;scotch&lt;/code&gt;的动态库，疑似需要调整scotch的cmake参数。检查结果发现nix上有两个相关的库（&lt;code&gt;scotch&lt;/code&gt;，&lt;code&gt;parmetis&lt;/code&gt;）都只是按静态链接库编译，所以OpenFOAM动态链接了个寂寞。&lt;/p&gt;
&lt;p&gt;调整两个包之后再次尝试编译OpenFOAM，发现依赖还是在编译期间用上。不得已检查了OpenFOAM的构建系统是如何判断库的位置。先从既有编译日志的“错误”信息入手搜索：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;=&amp;gt; skip scotch (no header)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;直接搜索&lt;code&gt;skip scotch&lt;/code&gt;，这么巧，一次就给我找到了：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Search&lt;/span&gt;
&lt;span class="c1"&gt;# $1 : prefix (*_ARCH_PATH, system, ...)&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# On success, return 0 and export variables&lt;/span&gt;
&lt;span class="c1"&gt;# -&amp;gt; HAVE_SCOTCH, SCOTCH_ARCH_PATH, SCOTCH_INC_DIR, SCOTCH_LIB_DIR&lt;/span&gt;
search_scotch&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;...
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;接着找调用它的代码，最后确定它的搜索行为受&lt;code&gt;SCOTCH_ARCH_PATH&lt;/code&gt;变量的影响。搜索这个变量发现，&lt;code&gt;bin/tools/foamConfigurePaths&lt;/code&gt;这个脚本会设置它。到最后只是我的编译配置阶段写错了么……其它库也得一起改，所以正确的配置是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# freecad-cfd.nix&lt;/span&gt;
&lt;span class="ss"&gt;configurePhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;''&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;  ...&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;  ./bin/tools/foamConfigurePaths \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    -boost-path &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;boost&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt; \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    -cgal-path &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;cgal&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt; \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    -fftw-path &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;fftw&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt; \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    -metis-path &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;parmetis&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt; \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    -paraview-path &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;paraview&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt; \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    -scotch-path &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;scotch&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt; \&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    ;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;  ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;从日志来看，相关动态库终于用上了，但是涉及cgal的代码编译失败了！报错基本是下面这种：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;include&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;CGAL&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;type_traits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;remove_cv_t&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;std&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;does&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;did&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;remove_cv&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;remove_cv_t&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;remove_reference_t&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="o"&gt;^~~~~~~~~~~&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;remove_cv&lt;/span&gt;
&lt;span class="nx"&gt;include&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;CGAL&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;Rational_traits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;83&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;enable_if_t&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;been&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;declared&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;83&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;Rational&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;make_rational&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;N&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;D&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;enable_if_t&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;is_implicit_convertible&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;RT&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;is_implicit_convertible&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;RT&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;const&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="nx"&gt;include&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;CGAL&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;Rational_traits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;83&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;','&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;'&amp;lt;'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;83&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;Rational&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;make_rational&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;N&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;D&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;enable_if_t&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;is_implicit_convertible&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;RT&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;is_implicit_convertible&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;RT&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;const&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;随便搜索一下报错，得到的是node相关的编译问题，加上cgal再次搜索，发现需要提高一下C++标准版本。默认情况下是C++11，改到C++14后编译通过了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;网上搜索找到了OpenFOAM C++编译配置文件位置为&lt;code&gt;src/wmake/rules/darwin64Clang/c++&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;发现了&lt;code&gt;FOAM_EXTRA_CXXFLAGS&lt;/code&gt;这个可疑变量&lt;/li&gt;
&lt;li&gt;进而在&lt;code&gt;src/etc/bashrc&lt;/code&gt;发现了这个变量的说明，可由用户配置&lt;/li&gt;
&lt;li&gt;最后在configurePhase中将&lt;code&gt;export FOAM_EXTRA_CXXFLAGS='-std=c++14'&lt;/code&gt;加入&lt;code&gt;src/etc/prefs.sh&lt;/code&gt;中，作为用户的额外配置&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;吐槽一下网上把新版代码改回旧版来解决问题的做法&lt;/p&gt;
&lt;p&gt;问题解决了，好方法；但是没理解问题的本质，以后再遇到可能会有更大的坑。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;也生成了对应的编译产物。这下总行了吧！&lt;strong&gt;好吧，进了终端还是不行。&lt;/strong&gt;提示找不到&lt;code&gt;libmpi.so.40&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;检查发现，openmpi相关库的位置是通过执行&lt;code&gt;mpicc --showme:link&lt;/code&gt;来动态获取，&lt;code&gt;mpicc&lt;/code&gt;在openmpi这个包里，但作为运行环境需要一直存在。所以在OpenFOAM包中还需要配置下面的内容，带上openmpi一起（不然就要单独在外面加这个包，依赖关系错乱了）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# openfoam/default.nix&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
stdenv&lt;span class="o"&gt;.&lt;/span&gt;mkDerivation &lt;span class="k"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="c1"&gt;# 没错我连paraview也一起带上了&lt;/span&gt;
  &lt;span class="ss"&gt;propagatedBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; openmpi paraview &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;宿主环境会影响nix-shell环境&lt;/p&gt;
&lt;p&gt;通过nix-shell使用&lt;em&gt;交互式&lt;/em&gt;终端，一样会source宿主的bashrc；而当Arch宿主安装FreeCAD时，同样会装openmpi，所以我之前没能发觉openmpi的问题。构建过程中宿主不会影响nix构建环境内部。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;至此OpenFOAM真的打包好了。至少demo能正常运行。&lt;/p&gt;
&lt;h3 id="打包cfmesh-cfdofhisa两个插件"&gt;打包&lt;code&gt;cfmesh-cfdof&lt;/code&gt;、&lt;code&gt;hisa&lt;/code&gt;两个插件&lt;a class="headerlink" href="#打包cfmesh-cfdofhisa两个插件" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;原本这两个插件都可以由CfdOF工作台自己安装，但本着创建可复现环境的心，尝试把它们也打包成nix的软件包（derivation）。打算先复用OpenFOAM软件包的框架，改改编译指令看看能不能成功。&lt;/p&gt;
&lt;p&gt;下载了源码，看了下都用和OpenFOAM一致的&lt;code&gt;wmake&lt;/code&gt;系统来构建，那估计编译也是执行根目录的&lt;code&gt;Allwmake&lt;/code&gt;脚本。但是没法一下子看懂编译产物究竟放哪里去了。&lt;/p&gt;
&lt;p&gt;先直接用装好OpenFOAM的nix-shell环境编译一遍看看（注意要记得加载OpenFOAM环境配置），查查最后的可执行文件（&lt;code&gt;hisa&lt;/code&gt;）都去哪里了。结果是&lt;code&gt;/home/user/OpenFOAM/user-v2306/platforms/linux64GccDPInt32Opt/bin&lt;/code&gt;，那我猜编译产物会自动丢到&lt;code&gt;/home/user/OpenFOAM/user-v2306/platforms/linux64GccDPInt32Opt&lt;/code&gt;。从环境变量的值来看，实际上编译产物会自动安装到&lt;code&gt;$WM_PROJECT_USER_DIR/platforms/$WM_OPTIONS/{bin,lib}&lt;/code&gt;中，而&lt;code&gt;$WM_PROJECT_USER_DIR&lt;/code&gt;与&lt;code&gt;$HOME&lt;/code&gt;有一定关联。考虑到在nix构建derivation过程中的&lt;code&gt;$HOME&lt;/code&gt;实际并不存在，则考虑修改&lt;code&gt;$HOME&lt;/code&gt;为临时文件夹应该就可以修改安装位置。最后将编译产物复制到&lt;code&gt;$out&lt;/code&gt;合适的位置中即可。&lt;/p&gt;
&lt;p&gt;脚本改改改……&lt;code&gt;buildInputs&lt;/code&gt;里加上&lt;code&gt;openfoam&lt;/code&gt;，毕竟这些东西要依赖OpenFOAM的工具编译、绑定对应的版本。运行一下nix-shell试试看。&lt;/p&gt;
&lt;p&gt;直接构建提示缺zlib？&lt;code&gt;zlib.h&lt;/code&gt;找不到了？看来是编译依赖没写全。把OpenFOAM的复制过来……啊不用，其实有更简洁的方法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ss"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; openfoam &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt; openfoam&lt;span class="o"&gt;.&lt;/span&gt;buildInputs&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;把OpenFOAM的依赖一起加进去！&lt;/p&gt;
&lt;p&gt;由于所使用的&lt;code&gt;cfmesh-cfdof&lt;/code&gt;及&lt;code&gt;hisa&lt;/code&gt;的源码来源并没有版本管理，为了让一切可以复现，将源码包一并附上。于是加载源码包的代码就变成了下面这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# In both cases, the zip is automatically extracted&lt;/span&gt;
&lt;span class="ss"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;version&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"bundled"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="l"&gt;./cfmesh-cfdof.zip&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    fetchzip &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://sourceforge.net/projects/cfmesh-cfdof/files/cfmesh-cfdof.zip/download"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;stdenv.mkDerivation默认会自动解压源码&lt;/p&gt;
&lt;p&gt;如果提供一个压缩包给&lt;code&gt;src&lt;/code&gt;属性，&lt;code&gt;stdenv.mkDerivation&lt;/code&gt;会尝试自动解压，此时需确保&lt;code&gt;nativeBuildInputs&lt;/code&gt;提供了解压所需工具。压缩包内单独存在的顶级文件夹会被缩简，默认工作目录会变成源码根目录。我曾因为一些操作和巧合，只编译了一半&lt;code&gt;hisa&lt;/code&gt;。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;使用stdenv.mkDerivation遇到的默认状况问题&lt;/p&gt;
&lt;p&gt;一开始没改动&lt;code&gt;configurePhase&lt;/code&gt;，没想到默认的居然会自动用cmake配置，一定记得视情况手动调整，用不到的话可以加一个&lt;code&gt;dontUseCmakeConfigure = true;&lt;/code&gt;。同理&lt;code&gt;buildPhase&lt;/code&gt;之类也有自己的默认配置，需要注意。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;总之到最后搞定了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;不知为何写到这里已经没什么动力继续写环境配置的细节问题了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="一些小修小补"&gt;一些小修小补&lt;a class="headerlink" href="#一些小修小补" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;除了特殊配置，甚至还要修一些bug，真是没想到。&lt;/p&gt;
&lt;h4 id="freecad的数据存储位置最好不要在家目录吧"&gt;FreeCAD的数据存储位置最好不要在家目录吧&lt;a class="headerlink" href="#freecad的数据存储位置最好不要在家目录吧" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;想让整个环境portable的话，肯定不能丢家目录了。经&lt;code&gt;freecad -h&lt;/code&gt;指引，我找到了&lt;a href="https://wiki.freecad.org/Start_up_and_Configuration"&gt;FreeCAD的官方启动配置文档&lt;/a&gt;，添加了几个环境变量。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ss"&gt;shellHook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;''&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    ...&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    export FREECAD_USER_HOME=$(pwd)/freecad-state/home&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    export FREECAD_USER_DATA=$(pwd)/freecad-state/userdata&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    export FREECAD_USER_TEMP=$(pwd)/freecad-state/temp&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    ...&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="cfdof检查paraview版本时得到了错误的版本信息"&gt;CfdOF检查paraview版本时得到了错误的版本信息&lt;a class="headerlink" href="#cfdof检查paraview版本时得到了错误的版本信息" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;CfdOF得到的paraview版本号是&lt;code&gt;"canberra-gtk-module"&lt;/code&gt;，没错还包括引号。很奇怪啊。&lt;/p&gt;
&lt;p&gt;直接在命令行看看paraview的版本号：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paraview&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;
&lt;span class="n"&gt;Gtk&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;34.014&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"canberra-gtk-module"&lt;/span&gt;
&lt;span class="n"&gt;Gtk&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;34.015&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"canberra-gtk-module"&lt;/span&gt;
&lt;span class="n"&gt;paraview&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.11&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paraview&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;/&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb nb-Type"&gt;null&lt;/span&gt;
&lt;span class="n"&gt;paraview&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.11&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;看样子检测版本的时候把标准错误输出流的内容也处理了，通常情况下不需要读stderr才对。&lt;/p&gt;
&lt;p&gt;修改了一下代码，正常了。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gd"&gt;--- a/CfdOF/CfdTools.py&lt;/span&gt;
&lt;span class="gi"&gt;+++ b/CfdOF/CfdTools.py&lt;/span&gt;
&lt;span class="gu"&gt;@@ -1178,7 +1178,7 @@ def checkCfdDependencies(msgFn):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;            proc.setProcessEnvironment(env)
&lt;span class="w"&gt; &lt;/span&gt;            proc.start()
&lt;span class="w"&gt; &lt;/span&gt;            if proc.waitForFinished():
&lt;span class="gd"&gt;-                pvversion = proc.readAllStandardOutput() + proc.readAllStandardError()&lt;/span&gt;
&lt;span class="gi"&gt;+                pvversion = proc.readAllStandardOutput()&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;                pvversion = QTextStream(pvversion).readAll().split()
&lt;span class="w"&gt; &lt;/span&gt;                # The --version flag doesn't seem to work on Winodws, so quietly ignore if nothing returned
&lt;span class="w"&gt; &lt;/span&gt;                if len(pvversion):
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="把几个软件包丢一个包里"&gt;把几个软件包丢一个包里&lt;a class="headerlink" href="#把几个软件包丢一个包里" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;nixpkgs能这么做，nixgl能这么做，我也要这么做！&lt;/p&gt;
&lt;p&gt;相当于搓了一个库，刚好也可以复用既有代码支持多个版本：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; pkgs &lt;span class="p"&gt;}:&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt;
  &lt;span class="ss"&gt;openfoam-2306&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; pkgs&lt;span class="o"&gt;.&lt;/span&gt;callPackage &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;import&lt;/span&gt; &lt;span class="l"&gt;./openfoam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="ss"&gt;openfoam-2312&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; openfoam-2306&lt;span class="o"&gt;.&lt;/span&gt;override &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2312"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="ss"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0wgfyz4q7xr0vlv4znfxpxyp8jb53q5pl17k312dyb3x8gkdx2z4"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="ss"&gt;cfmesh-cfdof&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; pkgs&lt;span class="o"&gt;.&lt;/span&gt;callPackage &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;import&lt;/span&gt; &lt;span class="l"&gt;./cfmesh-cfdof&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;openfoam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; openfoam-2306&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="ss"&gt;hisa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; pkgs&lt;span class="o"&gt;.&lt;/span&gt;callPackage &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;import&lt;/span&gt; &lt;span class="l"&gt;./hisa&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;openfoam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; openfoam-2306&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="ss"&gt;cfmesh-cfdof-unstable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; cfmesh-cfdof&lt;span class="o"&gt;.&lt;/span&gt;override &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"unstable"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="ss"&gt;hisa-unstable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; hisa&lt;span class="o"&gt;.&lt;/span&gt;override &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"unstable"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;inherit&lt;/span&gt; openfoam-2306 openfoam-2312&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;inherit&lt;/span&gt; cfmesh-cfdof hisa&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;inherit&lt;/span&gt; cfmesh-cfdof-unstable hisa-unstable&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不过用overlay的方式好像更合适。暂时不改了，除非之后要加&lt;code&gt;kahip&lt;/code&gt;。&lt;/p&gt;
&lt;h4 id="fem工作台检测不到已装的求解器"&gt;FEM工作台检测不到已装的求解器&lt;a class="headerlink" href="#fem工作台检测不到已装的求解器" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;想着流体分析的环境都搞定了，一块把刚体计算的部分补上呗，反正就多装几个求解器。&lt;a href="https://github.com/luzpaz/FreeCAD-dependencies"&gt;FreeCAD dependencies&lt;/a&gt;列出了很有用的信息，我在&lt;a href="https://search.nixos.org/"&gt;Nixpkgs搜索站点&lt;/a&gt;把FEM的依赖都搜索了一遍，发现还能多装两个有限元分析求解器：&lt;code&gt;calculix&lt;/code&gt;和&lt;code&gt;elmerfem&lt;/code&gt;。可用求解器数量大于零就不用我像之前给OpenFOAM打包那样了。&lt;/p&gt;
&lt;p&gt;把依赖加进去后构建shell，结果发现FreeCAD检测不到我装的求解器。由于nix不遵守&lt;a href="https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard"&gt;FHS&lt;/a&gt;，我没法写死求解器路径，所以只好想办法修bug。&lt;/p&gt;
&lt;p&gt;FreeCAD有选项开启自动从常见位置找求解器可执行文件，我猜它就是从&lt;code&gt;PATH&lt;/code&gt;环境变量里搜索那些东西。calculix的可执行文件是&lt;code&gt;ccx&lt;/code&gt;，以此出发，在FreeCAD源码中搜索，最终找到查找可执行文件的代码，其中有一段是这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// first check the environment paths by QFileInfo&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QFileInfo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QString&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;fromLatin1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binaryName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c_str&lt;/span&gt;&lt;span class="p"&gt;())))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;binaryName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;binaryName&lt;/code&gt;是可执行文件的名称，例如&lt;code&gt;ccx&lt;/code&gt;。这QT方法看着怪怪的，不像是找可执行文件的方法，倒像加载所给路径的方法（像python的sys.path）。&lt;/p&gt;
&lt;p&gt;在自建LLM、搜索引擎、QT官方文档等的联合帮助下（火热的ChatGPT恰好失效了），我搓了一个基本的测试程序验证了我的想法（毕竟直接重新编译FreeCAD并验证太费劲）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// main.cpp&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;QFileInfo&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;QStandardPaths&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;QString&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;iostream&amp;gt;&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cerr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Usage: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;" &amp;lt;path to executable&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;QString&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;executableName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;QString&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;executablePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;QStandardPaths&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;findExecutable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executableName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;QFileInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fileInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fileInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fileInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isExecutable&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"The binary exists and is executable."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Binary location: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toStdString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"FreeCAD's code test result: "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QFileInfo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executableName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"found"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"not found"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"The binary does not exist or is not executable."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;所以后面用&lt;code&gt;QStandardPaths::findExecutable&lt;/code&gt;来替代了&lt;code&gt;QFileInfo::exists&lt;/code&gt;，修正了相关问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但仍然有个问题：如何整合进nix脚本？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;结果使用了&lt;code&gt;overrideAttrs&lt;/code&gt;这个方法来改写原本包的属性，加上自己的补丁：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# some bug hotfix, only for freecad 0.21.2&lt;/span&gt;
&lt;span class="ss"&gt;freecad-patched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; pkgs&lt;span class="o"&gt;.&lt;/span&gt;freecad&lt;span class="o"&gt;.&lt;/span&gt;overrideAttrs &lt;span class="p"&gt;(&lt;/span&gt;finalAttrs&lt;span class="p"&gt;:&lt;/span&gt; previousAttrs&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;patches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; previousAttrs&lt;span class="o"&gt;.&lt;/span&gt;patches &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; 
        &lt;span class="l"&gt;./patches/0001-Fem-fix-searching-3rd-party-binaries-in-system-path.patch&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt; 
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;patches属性为什么能生效？&lt;/p&gt;
&lt;p&gt;nix的软件包用到了nix标准库中的&lt;code&gt;stdenv.mkDerivation&lt;/code&gt;这个工具，可以理解为某种函数或编译脚本，根据参数生成编译成果。&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="物理仿真实战"&gt;物理仿真实战&lt;a class="headerlink" href="#物理仿真实战" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;折腾了那么久软件环境，总该干点实在的对吧！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;软件环境声明&lt;/p&gt;
&lt;p&gt;以下内容均基于FreeCAD 0.21.2，所有软件环境可用文末提到的nix-shell配置复现。&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="亚克力弹性按钮仿真"&gt;亚克力弹性按钮仿真&lt;a class="headerlink" href="#亚克力弹性按钮仿真" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;亚克力/PMMA是常用外壳板材。通过合适的切割，可以用亚克力实现一些方便的弹性组件。不过这个对切割精度有一定要求，有些亚克力厂可能做不了，或者因为怕损失不接单。&lt;/p&gt;
&lt;p&gt;&lt;img alt="亚克力外壳上切割出的弹性按钮" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/fem-pics/acrylic-flexible-button-product.jpeg"/&gt;&lt;/p&gt;
&lt;p&gt;作为示例，我就简单绘制一个弹性按钮3D模型作为示范。如何建模我就不详细写了，完整的示例模型见&lt;a href="https://techie-s.work/posts/2024/06/freecad-physics-simulation/acrylic-flexible-button-example.FCStd"&gt;模型附件&lt;/a&gt;。参考材质为3mm厚的PMMA板材。&lt;/p&gt;
&lt;p&gt;&lt;img alt="弹性按钮模型图例" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/fem-pics/acrylic-flexible-button-screenshot.png"/&gt;&lt;/p&gt;
&lt;p&gt;参考&lt;a href="https://wiki.freecad.org/FEM_tutorial"&gt;FreeCAD官方的FEM教程&lt;/a&gt;，依次操作。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;切换到FEM工作台
  &lt;img alt="切换到FEM工作台" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/fem-pics/step1-switch-to-fem.png"/&gt;&lt;/li&gt;
&lt;li&gt;创建用于存放各种分析配置的分析容器实例
  &lt;img alt="创建分析实例" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/fem-pics/step2-create-analysis-container.png"/&gt;&lt;/li&gt;
&lt;li&gt;创建所需约束，本案例仅需选择固定面、受力面。由于约束与面相关，按钮中间需要用某种方法单独划块受力面（比如画个草图pad一下）。此处配置受力面受力1N。
  &lt;img alt="添加固定的侧面" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/fem-pics/step3-1-create-constraints-fixed.png"/&gt;
&lt;img alt="添加受力面" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/fem-pics/step3-2-create-constraints-force.png"/&gt;
&lt;img alt="完成图" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/fem-pics/step3-3-create-constraints-finished.png"/&gt;&lt;/li&gt;
&lt;li&gt;添加模型材质。这里选择亚克力。
  &lt;img alt="添加模型的材质设定，选择亚克力" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/fem-pics/step4-add-material.png"/&gt;&lt;/li&gt;
&lt;li&gt;生成模型网格。官方教程说建议把这步放在准备工作里的最后一步，因为牵扯到模型。这里使用&lt;code&gt;gmsh&lt;/code&gt;生成网格。先选中模型，再点击按钮，在新的菜单中先点击Apply，再点击OK，单纯点OK不会更新网格模型。如果提示gmsh报warning了，可以多点几次Apply重试，不然可能影响到后续求解器计算。
  &lt;img alt="生成模型网格" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/fem-pics/step5-meshing.png"/&gt;&lt;/li&gt;
&lt;li&gt;创建求解器对象。这里需要选择与你安装的求解器匹配的选项。如果使用了文末提供的nix-shell环境，则可以使用&lt;code&gt;calculix&lt;/code&gt;和&lt;code&gt;elmer&lt;/code&gt;求解器。此处使用&lt;code&gt;calculix&lt;/code&gt;。
  &lt;img alt="创建求解器" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/fem-pics/step6-create-solver.png"/&gt;&lt;/li&gt;
&lt;li&gt;双击刚刚创建的求解器对象，选择合适的分析类型后，依次点击&lt;code&gt;Write .inp file&lt;/code&gt;、&lt;code&gt;Run CalculiX&lt;/code&gt;按钮。计算结果就出来了，但现在什么都看不到，需要进一步处理。
  &lt;img alt="运行求解器" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/fem-pics/step7-use-solver.png"/&gt;&lt;/li&gt;
&lt;li&gt;选中结果，点击显示结果（&lt;code&gt;Show Result&lt;/code&gt;）按钮，在打开的菜单中，选中&lt;code&gt;Max Sheer Stress&lt;/code&gt;可以看到受力情况。在下方Displacement处勾选Show并调整Factor即可观察形变情况。
  &lt;img alt="点击显示结果" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/fem-pics/step8-click-show-result.png"/&gt;
&lt;img alt="查看仿真结果" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/fem-pics/step8-show-result.png"/&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;至此已完成亚克力板受力仿真分析。&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;示例模型不适合加工&lt;/p&gt;
&lt;p&gt;示例模型过于精细，与实物图例有较大差异，加工比较困难，良率应该不高，切勿直接套用。&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="导风漏斗气流仿真"&gt;导风漏斗气流仿真&lt;a class="headerlink" href="#导风漏斗气流仿真" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;这是我最初想解决的问题，怎么拖到了最后！这也没有现成的教程文档，有点难啊！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;CfdOF项目README写道，还没有正式的文档可用，但有几个Demo可以看。考虑到我想做的基本是个风机管道（duct），我就打算参考&lt;a href="https://github.com/jaheyns/CfdOF/tree/master/Demos/Duct"&gt;duct示例&lt;/a&gt;来做做看了。&lt;a href="https://techie-s.work/posts/2024/06/freecad-physics-simulation/funnel-example.FCStd"&gt;点我获取本案例的模型文件&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;Demo中提供了几个宏程序，可以快速构建一个示例模型，只需要计算即可。但我肯定要自己手动在我自己的模型上操作一遍啦。基本流程和FEM差不多。&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;缺失的计算流体动力学知识&lt;/p&gt;
&lt;p&gt;笔者并没有流体动力学专业背景，也没有积累相应的知识，因此不能深入理解一些配置选项，选取的数值可能很离谱，总之可能造成一些误解，务必小心。&lt;/p&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;构建气流管道模型。注意在仿真时用的模型是流体流过的地方。这里我制作了一个参数化的管道，并单独构建了气流区域的模型。
  &lt;img alt="管道模型" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/cfdof-pics/step1-the-model.png"/&gt;&lt;/li&gt;
&lt;li&gt;切换到CfdOF工作台，添加分析方案容器。此时会多一个&lt;code&gt;analysis-container&lt;/code&gt;，以及其下的四个配置项：物理模型、流体属性、流体初始态、CFD求解器。不过暂时先不改。
  &lt;img alt="添加分析方案容器" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/cfdof-pics/step2-create-analysis.png"/&gt;&lt;/li&gt;
&lt;li&gt;将管道模型的面标记为对应类型，设置流体边界。在菜单中点击&lt;code&gt;Add&lt;/code&gt;后点击需要添加的面。Inlet面设置的气压是&lt;code&gt;1.28e+02 kPa&lt;/code&gt;，数值是我根据手上的管道风机猜的。此处分别添加wall、需注意由于文件中有多个模型，不建议设置默认面属性，以免造成影响。此外建议不要移动目标物体的位置，不然会有奇妙的视觉效果 :)。
  &lt;img alt="设置流体边界" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/cfdof-pics/step3-0-set-fluid-boundary.png"/&gt;
&lt;img alt="流体边界设置完毕" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/cfdof-pics/step3-2-finish-adding-boundary.png"/&gt;&lt;/li&gt;
&lt;li&gt;回到之前略过的，设置流体初始状态。实话说我不知道怎么讲解，设置了一下初始压强。
  &lt;img alt="设置流体初始状态" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/cfdof-pics/step4-initialize-flow.png"/&gt;&lt;/li&gt;
&lt;li&gt;设置物理模型。查了下术语后，觉得这里应该选个湍流模型。
  &lt;img alt="物理模型设置菜单" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/cfdof-pics/step5-set-physics-model.png"/&gt;&lt;/li&gt;
&lt;li&gt;设置流体属性。有个空气模板我就直接选了。
  &lt;img alt="流体属性设置菜单" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/cfdof-pics/step6-set-fluid-properties.png"/&gt;&lt;/li&gt;
&lt;li&gt;添加模型网格。先选中管道模型，再点击添加网格的按钮。在菜单中依次点击&lt;code&gt;Write mesh case&lt;/code&gt;和&lt;code&gt;Run mesher&lt;/code&gt;。
  &lt;img alt="点击网格化" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/cfdof-pics/step7-1-meshing.png"/&gt;
&lt;img alt="模型网格化菜单cfmesh" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/cfdof-pics/step7-2-meshing-menu.png"/&gt;
&lt;img alt="模型网格化菜单但是snappyHexMesh" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/cfdof-pics/step7-2-meshing-menu-2.png"/&gt;
&lt;img alt="网格结果" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/cfdof-pics/step7-3-mesh-result.png"/&gt;&lt;/li&gt;
&lt;li&gt;双击CFD求解器，接下来可以进行计算了。依次点击&lt;code&gt;Write&lt;/code&gt;、&lt;code&gt;Run&lt;/code&gt;进行计算。最后点击Paraview查看结果。流程基本到此
  &lt;img alt="CFD求解器设置菜单" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/cfdof-pics/step8-1-analysis-menu.png"/&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但是这也不是一帆风顺！我按自己的猜想对模型的面进行划分，结果却导致了求解器的计算错误：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Caught&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;signal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Floating&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tkill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tgkill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;于是我选择简化并优先完成一个简单模拟。下图是最后的结果（我也看不明白……）。&lt;/p&gt;
&lt;p&gt;&lt;img alt="简化的模型" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/cfdof-pics/simplified-model.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="CFD最终结果" src="https://techie-s.work/posts/2024/06/freecad-physics-simulation/cfdof-pics/final-result.png"/&gt;&lt;/p&gt;
&lt;p&gt;看起来需要补一些专业知识了，不然仿真计算的意义也不大。&lt;/p&gt;
&lt;h2 id="总结与补充说明"&gt;总结与补充说明&lt;a class="headerlink" href="#总结与补充说明" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;为了用FreeCAD进行流体仿真计算，尝试了各种方法，后面成功使用nix-shell构建了一个稳定可复现的软件环境，顺带修了CfdOF和FreeCAD的小错误。&lt;/p&gt;
&lt;p&gt;在完成FreeCAD可复现环境的构建后，根据现实需求，给出了两个FreeCAD物理仿真案例及其流程。&lt;/p&gt;
&lt;p&gt;完整的nix-shell环境项目公开在&lt;a href="https://github.com/hyx0329/freecad-nix-shell"&gt;我的github仓库&lt;/a&gt;，使用MIT协议授权分发，欢迎star！&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;关于项目许可证&lt;/p&gt;
&lt;p&gt;虽然项目使用MIT授权分发，但是项目也附带了&lt;code&gt;cfmesh&lt;/code&gt;和&lt;code&gt;hisa&lt;/code&gt;的源码zip包，&lt;strong&gt;它们是按GPL3协议授权的！&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="后记"&gt;后记&lt;a class="headerlink" href="#后记" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;因为这里的&lt;em&gt;博客&lt;/em&gt;都是写给自己看的，所以我经常写得很简略。其中也有自己急躁的因素就是了。此次难得补充一篇，想&lt;em&gt;稍微&lt;/em&gt;写细致一点，不过没想到极大量篇幅给了nix。希望读者们能读得开心。&lt;/p&gt;
&lt;p&gt;趁此机会，我把这个站点的脚本收拾了一遍，也用nix-shell写了声明式软件环境配置。至少是从NixOS那边汲取了有用的东西。也希望能启发各位读者。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;nix和rust还真有几分相似呢&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;另外之前遇到的FreeCAD玄学小问题好像都消失了٩( ᐛ )و&lt;/em&gt;&lt;/p&gt;</content><category term="Misc"/><category term="physics"/><category term="simulation"/><category term="nix"/><category term="freeware"/></entry><entry><title>用 Embassy 开发 Xinput 协议手柄</title><link href="https://techie-s.work/posts/2023/04/develop-xinput-controller-with-embassy/" rel="alternate"/><published>2023-04-17T00:00:00+08:00</published><updated>2023-04-17T12:16:07+08:00</updated><author><name/></author><id>tag:techie-s.work,2023-04-17:/posts/2023/04/develop-xinput-controller-with-embassy/</id><summary type="html">&lt;p&gt;Project repository: &lt;a href="https://github.com/hyx0329/em-usb-pad"&gt;GitHub repo&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Important thing:&lt;/p&gt;</summary><content type="html">
&lt;p&gt;Project repository: &lt;a href="https://github.com/hyx0329/em-usb-pad"&gt;GitHub repo&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="road-map"&gt;Road Map&lt;a class="headerlink" href="#road-map" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul class="task-list"&gt;
&lt;li class="task-list-item"&gt;&lt;input checked="" disabled="" type="checkbox"/&gt; proof of concept&lt;ul class="task-list"&gt;
&lt;li class="task-list-item"&gt;&lt;input checked="" disabled="" type="checkbox"/&gt; implement xinput on stm32f103&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input checked="" disabled="" type="checkbox"/&gt; implement keypad scan on stm32f103&lt;ul class="task-list"&gt;
&lt;li class="task-list-item"&gt;&lt;input checked="" disabled="" type="checkbox"/&gt; now use existing &lt;code&gt;keypad&lt;/code&gt; from crates.io&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input checked="" disabled="" type="checkbox"/&gt; act like a gamepad&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; implement keymap on stm32f103&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; port to nrf52840&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; implement ble (different modes)&lt;ul class="task-list"&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; hid keyboard &amp;amp; mouse&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; gamepad&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; improve overall structure&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; implement mode switch&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; board abstraction&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; package/lib?&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; custom board (prototype)&lt;ul class="task-list"&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; NFC&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; battery&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; rumble&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; LED&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; and custom shell&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; production design&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Important thing:&lt;/p&gt;
&lt;ul class="task-list"&gt;
&lt;li class="task-list-item"&gt;&lt;input disabled="" type="checkbox"/&gt; what about the marketing?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="about-bluetooth"&gt;about bluetooth&lt;a class="headerlink" href="#about-bluetooth" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;https://en.wikipedia.org/wiki/Xbox_Wireless_Controller#Summary&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Classic Bluetooth HID profile &lt;/li&gt;
&lt;li&gt;HID over GATT Profile&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I wonder if they are mostly the same protocol on a higher level, because there are evidences showing the existence of Bluetooth LE XInput compatible controller.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:usb-nutshell"&gt;
&lt;p&gt;&lt;a href="https://www.beyondlogic.org/usbnutshell/usb1.shtml"&gt;USB in a nutshell&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:usb-nutshell" title="回到 #1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:xbox360-usb-protocol"&gt;
&lt;p&gt;&lt;a href="https://www.partsnotincluded.com/understanding-the-xbox-360-wired-controllers-usb-data/"&gt;Understanding the Xbox 360 Wired Controller’s USB Data&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:xbox360-usb-protocol" title="回到 #2"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:gamepad-tester"&gt;
&lt;p&gt;&lt;a href="https://gamepad-tester.com/"&gt;Gamepad Tester and Debugger
&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:gamepad-tester" title="回到 #3"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:arduinoxinput-avr"&gt;
&lt;p&gt;&lt;a href="https://github.com/dmadison/ArduinoXInput_AVR"&gt;ArduinoXInput-AVR&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:arduinoxinput-avr" title="回到 #4"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:arduinoxinput-lib"&gt;
&lt;p&gt;&lt;a href="https://github.com/dmadison/ArduinoXInput"&gt;ArduinoXInput-Library&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:arduinoxinput-lib" title="回到 #5"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="Embedded"/><category term="embassy"/><category term="xinput"/><category term="usb"/><category term="rust"/><category term="ble"/><category term="nrf52"/><category term="stm32"/></entry><entry><title>Embassy——易用的嵌入式Rust异步框架</title><link href="https://techie-s.work/posts/2023/01/embassy-an-easy-to-use-async-framework-for-embedded-rust/" rel="alternate"/><published>2023-01-25T00:00:00+08:00</published><updated>2023-01-25T00:00:00+08:00</updated><author><name/></author><id>tag:techie-s.work,2023-01-25:/posts/2023/01/embassy-an-easy-to-use-async-framework-for-embedded-rust/</id><summary type="html">&lt;p&gt;有些「小制作」需要用到USB、蓝牙、WiFi，我又想用 Rust 进行编程，但很多 Crates 提供的方法很有限。目前我粗略接触过的 &lt;code&gt;esp-idf-sys&lt;/code&gt; 、 &lt;code&gt;Tock OS&lt;/code&gt; 之类，都缺 …&lt;/p&gt;</summary><content type="html">
&lt;p&gt;有些「小制作」需要用到USB、蓝牙、WiFi，我又想用 Rust 进行编程，但很多 Crates 提供的方法很有限。目前我粗略接触过的 &lt;code&gt;esp-idf-sys&lt;/code&gt; 、 &lt;code&gt;Tock OS&lt;/code&gt; 之类，都缺少了一些我计划中需要的关键功能（这么看感觉 C 那边生态优势很明显，瞧瞧 &lt;code&gt;Zephyr RTOS&lt;/code&gt; 的成熟度）。最近在搜索 Rust 可用的蓝牙协议栈时，看到了 &lt;code&gt;Embassy&lt;/code&gt; 这个框架，第一感觉是帮我简化了很多 MCU 外设管理的冗杂操作，顿时留下不错的印象。下面是一些个人学习记录。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;之前觉得是给自己留的坑，写了一半没继续，如今看来还能有点用处，先挂出来吧。写得不咋地，还可能有错，见谅。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Embassy版本更新会带来一些API变更，建议从官方master分支的example开始建立代码工作区。本文不一定适用。&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="embassy-点灯"&gt;Embassy 点灯&lt;a class="headerlink" href="#embassy-点灯" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;废话不多说，先点个灯，好有个直观感受。嘛，还是得先配置好开发环境。先简单列出我的开发资源配置。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;笔记本电脑，操作系统为 Arch Linux&lt;/li&gt;
&lt;li&gt;开发板：WeAct BluePill Plus（STM32F103CBT6）&lt;/li&gt;
&lt;li&gt;调试器：Muse Lab 高速 DAPLink（兼容公版 ATSAM3U2C DAPLink）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;首先准备好 Rust 工具链，这个可以参考 &lt;code&gt;https://rustup.rs&lt;/code&gt; 提供的信息进行安装，此处不再详叙。&lt;/p&gt;
&lt;p&gt;由于我们的目标板 CPU 架构为 ARM Cortex-M，需要额外安装对应的 &lt;em&gt;target&lt;/em&gt; 。不过幸运的是，embassy 项目空间内已经写好了工具链配置（&lt;code&gt;rust-toolchain.toml&lt;/code&gt;)，待会跑 blinky 的时候 rustup 会自动处理工具链依赖。&lt;/p&gt;
&lt;p&gt;这边编程也 &lt;strong&gt;不用&lt;/strong&gt; 多数人熟悉的 OpenOCD 了（尽管也可以），使用的是基于 Rust Crate &lt;code&gt;probe-rs&lt;/code&gt; 的一系列工具。这里主要使用的是 &lt;em&gt;probe-run&lt;/em&gt; &lt;sup id="fnref:probe-run-on-crates"&gt;&lt;a class="footnote-ref" href="#fn:probe-run-on-crates"&gt;1&lt;/a&gt;&lt;/sup&gt; （这样可以直接跑官方例程）。使用下面的指令安装 probe-run。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 安装 probe-run&lt;/span&gt;
cargo&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;probe-run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;关于 probe-run 的配置，待进入示例项目再看。&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;如果 rustup、cargo 下载的速度过慢&lt;/p&gt;
&lt;p&gt;可以尝试一下配置使用其它镜像或加速站点 &lt;sup id="fnref:ustc-rs-tc"&gt;&lt;a class="footnote-ref" href="#fn:ustc-rs-tc"&gt;4&lt;/a&gt;&lt;/sup&gt; &lt;sup id="fnref:ustc-rs-crates"&gt;&lt;a class="footnote-ref" href="#fn:ustc-rs-crates"&gt;5&lt;/a&gt;&lt;/sup&gt; &lt;sup id="fnref:rsproxy"&gt;&lt;a class="footnote-ref" href="#fn:rsproxy"&gt;6&lt;/a&gt;&lt;/sup&gt;。需要额外注意的是，如果自己的 git 配置文件（&lt;code&gt;~/.gitconfig&lt;/code&gt;）中配置了代理，建议在操作 rustup 和 cargo 的过程中删除代理相关配置。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;开发板选择参考&lt;/p&gt;
&lt;p&gt;目前 Embassy 对 STM32、NRF52 的支持情况足够好，资源丰富，所以选相关开发板能省不少力。不过要是有余力，也可以去看看 Xtensa（ESP32）或者 RISC-V（ESP32-C3）架构的支持，截至2023-01-26，Embassy 已经有对它们的初步支持，而相关工具（probe-run）也支持了 ESP32-C3 内置的 JTAG 适配器。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;准备运行示例项目，先准备一份源代码，操作如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;--depth&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--recursive&lt;span class="w"&gt; &lt;/span&gt;https://github.com/embassy-rs/embassy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;随后进入适用于自己开发板 MCU 的示例项目文件夹。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;embassy/examples/stm32f1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;看一下示例项目内有哪些材料：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;tree&lt;span class="w"&gt; &lt;/span&gt;-a
.
├──&lt;span class="w"&gt; &lt;/span&gt;build.rs&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;# 最早编译并运行的 build script&lt;/span&gt;
├──&lt;span class="w"&gt; &lt;/span&gt;.cargo
│&lt;span class="w"&gt;   &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;config.toml&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="c1"&gt;# cargo 配置，关于 probe-run 的配置也在这里&lt;/span&gt;
├──&lt;span class="w"&gt; &lt;/span&gt;Cargo.lock&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="c1"&gt;# 锁，锁的是 Crates 版本&lt;/span&gt;
├──&lt;span class="w"&gt; &lt;/span&gt;Cargo.toml&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="c1"&gt;# 项目配置&lt;/span&gt;
└──&lt;span class="w"&gt; &lt;/span&gt;src&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="c1"&gt;# 这里有 4 个示例程序&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;bin
&lt;span class="w"&gt;        &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;adc.rs
&lt;span class="w"&gt;        &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;blinky.rs
&lt;span class="w"&gt;        &lt;/span&gt;├──&lt;span class="w"&gt; &lt;/span&gt;hello.rs
&lt;span class="w"&gt;        &lt;/span&gt;└──&lt;span class="w"&gt; &lt;/span&gt;usb_serial.rs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这次就跑个 blinky（&lt;code&gt;blinky.rs&lt;/code&gt;），检查下代码适不适合我的板子，一看，就 LED 引脚不一样。那就简单修改一下。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gd"&gt;-    let mut led = Output::new(p.PC13, Level::High, Speed::Low);&lt;/span&gt;
&lt;span class="gi"&gt;+    let mut led = Output::new(p.PB2, Level::High, Speed::Low);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;再检查一下 probe-run 的配置（在 &lt;code&gt;.cargo/config.toml&lt;/code&gt;）和我的 MCU 是否一致。一看，倒也不用改。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace STM32F103C8 with your chip as listed in `probe-run --list-chips`
&lt;span class="gd"&gt;- runner = "probe-run --chip STM32F103C8"&lt;/span&gt;
&lt;span class="gi"&gt;+ runner = "probe-run --chip STM32F103CB"&lt;/span&gt;

[build]
target = "thumbv7m-none-eabi"

[env]
DEFMT_LOG = "trace"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;接下来就连接好开发板，执行下面的指令编译、刷入固件。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cargo&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--bin&lt;span class="w"&gt; &lt;/span&gt;blinky
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;等编译、刷写完毕，应该就能看到板子上的 LED 在闪烁了。&lt;/p&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;关于 LED 闪烁时间准确性&lt;/p&gt;
&lt;p&gt;你可能会觉得闪烁时间和程序里写的 300 ms 差别有点大，我也不知道为什么。Debug 日志输出使用了RTT，应该影响不大；由此来看很可能是因为 Embassy 的非抢占式调度。谈起调度，可以了解下 &lt;em&gt;RTIC&lt;/em&gt; ，Embassy 和 RTIC 的关系可以说是各自的重点不同 &lt;sup id="fnref:embassy-rtic"&gt;&lt;a class="footnote-ref" href="#fn:embassy-rtic"&gt;7&lt;/a&gt;&lt;/sup&gt; 。&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="embassy-设计初探"&gt;Embassy 设计初探 &lt;sup id="fnref:embassy-by-layer"&gt;&lt;a class="footnote-ref" href="#fn:embassy-by-layer"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;a class="headerlink" href="#embassy-设计初探" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Embassy 以一个 Executor 为核心。所有的任务用一个宏标记。以 &lt;code&gt;blinky.rs&lt;/code&gt; 为例：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#![no_std]&lt;/span&gt;
&lt;span class="cp"&gt;#![no_main]&lt;/span&gt;
&lt;span class="cp"&gt;#![feature(type_alias_impl_trait)]&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;defmt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;embassy_executor&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Spawner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;embassy_stm32&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;gpio&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Speed&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;embassy_time&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;defmt_rtt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;panic_probe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cp"&gt;#[embassy_executor::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_spawner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Spawner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;embassy_stm32&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;led&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PB2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;High&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Speed&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Low&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;loop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;led&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_high&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;from_millis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;led&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_low&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;from_millis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 blinky 的例子中，只是用宏标记了一个 &lt;code&gt;main&lt;/code&gt; 入口点（此处已简化许多），Embassy 在完成一定初始化后会从这里开始执行主程序。但是这样好像同时只有一个任务在运行？也确实如此，毕竟还有一个 &lt;code&gt;Task&lt;/code&gt; 没有引入。来看官方提供的另一个例子。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#![feature(type_alias_impl_trait)]&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;embassy_executor&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Spawner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;embassy_time&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cp"&gt;#[embassy_executor::task]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;loop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tick"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cp"&gt;#[embassy_executor::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spawner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Spawner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;env_logger&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;LevelFilter&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format_timestamp_nanos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;spawner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="n"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;此处便可以看到，通过 Embassy 提供的宏，创建了一个 Task，而通过调用这个 Task，便可以得到一个 task 实例（实际上和 coroutine 差不多）。有过异步编程经验的人应该很快就能认出这个简单的创建多个任务的模式（个人感觉和 CircuitPython 的使用逻辑很接近）。&lt;/p&gt;
&lt;p&gt;回到之前所说的，Embassy 的核心是一个 Executor。根据官方文档 &lt;sup id="fnref:embassy-executor"&gt;&lt;a class="footnote-ref" href="#fn:embassy-executor"&gt;3&lt;/a&gt;&lt;/sup&gt; 的介绍：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;executor 维护一个存储需要轮询（poll）的任务队列。当一个任务（task）创建时，它就被检视（poll）一次。这个任务会一直执行到它被阻塞。这可能发生在任何等待（await） async 函数返回结果的时候。而当前述事件发生时，对应任务会暂停执行（yield）并返回 &lt;code&gt;Poll:Pending&lt;/code&gt; 。随后，executor 将该任务放在任务队列尾部，去检视下一个任务。如果一个任务完成或者取消了，它就不再入队。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当然，这也有个前提：&lt;strong&gt;没有哪个任务会一直阻塞、不提供切换任务的机会。&lt;/strong&gt; 不然 executor 将永远无法切换任务，整个系统也退化成了单道程序处理机。&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:probe-run-on-crates"&gt;
&lt;p&gt;&lt;a href="https://crates.io/crates/probe-run"&gt;Crates.io 上的 probe-run&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:probe-run-on-crates" title="回到 #1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:embassy-by-layer"&gt;
&lt;p&gt;&lt;a href="https://embassy.dev/dev/layer_by_layer.html"&gt;Embassy layer by layer&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:embassy-by-layer" title="回到 #2"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:embassy-executor"&gt;
&lt;p&gt;&lt;a href="https://embassy.dev/dev/runtime.html"&gt;Embassy executor&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:embassy-executor" title="回到 #3"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:ustc-rs-tc"&gt;
&lt;p&gt;&lt;a href="https://mirrors.ustc.edu.cn/help/rust-static.html"&gt;USTC Rust 工具链反向代理配置说明&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:ustc-rs-tc" title="回到 #4"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:ustc-rs-crates"&gt;
&lt;p&gt;&lt;a href="https://mirrors.ustc.edu.cn/help/crates.io-index.html"&gt;USTC Crates 源使用帮助&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:ustc-rs-crates" title="回到 #5"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:rsproxy"&gt;
&lt;p&gt;&lt;a href="https://rsproxy.cn/"&gt;Rsproxy 主页&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:rsproxy" title="回到 #6"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:embassy-rtic"&gt;
&lt;p&gt;&lt;a href="https://cohost.org/jamesmunns/post/67555-should-i-use-embassy"&gt;Should I use Embassy or RTIC or both (for embedded rust)? – James Munns&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:embassy-rtic" title="回到 #7"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="Embedded"/><category term="embassy"/><category term="rust"/></entry><entry><title>蓝牙 SBC 双通道高清音频模式</title><link href="https://techie-s.work/posts/2023/01/lineage-bluetooth-sbc-xq/" rel="alternate"/><published>2023-01-22T00:00:00+08:00</published><updated>2023-01-22T00:00:00+08:00</updated><author><name/></author><id>tag:techie-s.work,2023-01-22:/posts/2023/01/lineage-bluetooth-sbc-xq/</id><summary type="html">&lt;p&gt;&lt;em&gt;本文是原Lineage官网列出的“Bluetooth SBC Dual Channel HD audio mode”&lt;sup id="fnref:original-bluetooth-sbc-xq"&gt;&lt;a class="footnote-ref" href="#fn:original-bluetooth-sbc-xq"&gt;1&lt;/a&gt;&lt;/sup&gt;的个人翻译版本，供各位学习交流使用。译文中的图片均直接转载自原文。原文作者是 Valdikss，发 …&lt;/em&gt;&lt;/p&gt;</summary><content type="html">
&lt;p&gt;&lt;em&gt;本文是原Lineage官网列出的“Bluetooth SBC Dual Channel HD audio mode”&lt;sup id="fnref:original-bluetooth-sbc-xq"&gt;&lt;a class="footnote-ref" href="#fn:original-bluetooth-sbc-xq"&gt;1&lt;/a&gt;&lt;/sup&gt;的个人翻译版本，供各位学习交流使用。译文中的图片均直接转载自原文。原文作者是 Valdikss，发布时间为2019年7月6日，引用时间2023年1月22日。&lt;/em&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;LineageOS 15.1 和 16.0 为原有的蓝牙 SBC 音频编码带来了一个独特的音质提升。它们引入了带有 eXtreme quality profile (SBC XQ) 的 SBC 双通道模式，将这个老旧编码的音频质量提升到了与高通私有编码——aptX HD——相近的高度。&lt;/p&gt;
&lt;p&gt;只要用户的设备运行的是在2019年3月31日或之后编译的 LineageOS 15.1、或是在2019年5月13日或之后编译的 LineageOS 16.0，一个新的“HD Audio: SBC”复选框就会出现在用户的蓝牙音频设备设置界面上。激活这项设定将启用双通道模式（Dual Channel mode），这提供了一个经过提升、比原版 SBC 编码高得多的比特率（bitrate）。新的比特率（452 或 551 kbps）依赖于蓝牙设备的最大增强数据率（Enhanced Data Rate）并且在使用联合立体声模式（Joint Stereo mode）时相比于原版 328 kbps 是一个明显的提升。&lt;/p&gt;
&lt;p&gt;这个特性搭载在所有受 LineageOS 支持的设备上，并且可以和现存的绝大多数蓝牙播放设备一同使用。这个特性最适合没有额外编码支持——aptX、aptX HD、AAC 或是 LDAC——的廉价音频设备。&lt;/p&gt;
&lt;h2 id="sbc-编码协议"&gt;SBC 编码（协议）&lt;a class="headerlink" href="#sbc-编码协议" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;SBC 有许多在连接过程中协商的不同参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;音频通道/音轨（类型和数量）：联合立体声，立体声，双通道，单声道；&lt;/li&gt;
&lt;li&gt;频带数量：4 或 8；&lt;/li&gt;
&lt;li&gt;单个音频数据帧中音频数据块的数量：4，8，12，16；&lt;/li&gt;
&lt;li&gt;量化位分配算法：响度，信噪比（SNR）；&lt;/li&gt;
&lt;li&gt;量化过程中用到比特池（bit pool）的容量范围：通常为 2 到 53；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;解码器（播放设备）应当支持上述参数的任何可能组合，但编码器（智能手机）可以只实现一部分。&lt;/p&gt;
&lt;p&gt;现在的蓝牙协议栈通常用下面一组参数进行协商（这一组参数也被称作 profile）：联合立体声（Joint Stereo），8 个频带，16 块，响度，以及一个 2 到 53 位的比特池大小。这个 profile 将用 328 kbps 的比特率为 44.1 kHz 的立体声音频编码。&lt;/p&gt;
&lt;p&gt;在所提到的参数中，比特池因其对比特率的直接影响处于格外重要的地位：它的大小越大，比特率就越大，也就提高了音频传输质量。当 profile 中的其他参数不改变时，一个特定的比特池大小将决定一个对应的比特率数值。&lt;/p&gt;
&lt;p&gt;比特率同样被其它参数显著影响：音轨类型、频带数量以及音频数据块数量。你可以通过协商非常规的 profiles 来间接提高比特率，而不用调整比特池大小。需要着重注意的是一个更高的比特率并不总是带来更好的音质。例如，将单个音频数据帧中的音频数据块数量从 16 下调到 4，当使用联合立体声模式和 53 位大小的比特池时，将会把比特率从 328 kbps 提升到 441 kbps，但这也将减少很多计算时延，而这样会让动态量化过程变得更糟糕。上述例子不会带来除比特率增长之外的任何好处。&lt;/p&gt;
&lt;div class="math"&gt;$$
bitrate = \frac{8 \times frame\_length \times sample\_rate}{subbands \times blocks}
$$&lt;/div&gt;
&lt;!-- ![bitrate计算公式]({attach}content_bluetooth_sbc_xq_0.png) --&gt;
&lt;p&gt;固定的比特池大小与比特率的值均来自原版编码为高品质音频推荐的 profile。这个推荐值应该不构成我们去限制实际参数值的原因，正如下面所做的那样。&lt;/p&gt;
&lt;p&gt;&lt;img alt="几种 profile 的参数对比" src="https://techie-s.work/posts/2023/01/lineage-bluetooth-sbc-xq/content_bluetooth_sbc_xq_2.png"/&gt;&lt;/p&gt;
&lt;p&gt;生效自 2007 年至 2015 年的 A2DP 规范 v1.2（译注：节4.3.2.6）要求所有解码设备都能正确处理最高达 512 kbps 的比特率：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The decoder of the SNK shall support all possible bitpool values that do not result in excess of the maximum bit rate. This profile limits the available maximum bit rate to 320kb/s for mono, and 512kb/s for two-channel modes.&lt;/p&gt;
&lt;p&gt;输入端（SNK，sink）的解码器必须支持所有不让比特率超过最大限定值的比特池大小。该 profile 将最大比特率限制为单声道模式下的 320kb/s 和双声道模式下的 512kb/s。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;而新版本的规范并未声明任何比特率限制。据推测，2015年之后发布的支持 EDR（Enhanced Data Rate）的现代耳机最高能支持 1000 kbps 的比特率。&lt;/p&gt;
&lt;h2 id="双通道模式"&gt;双通道模式&lt;a class="headerlink" href="#双通道模式" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;双通道模式分别对左右声道进行编码，每个声道都用单独的比特池大小，这不像立体声或联合立体声那样对两个声道用同一个比特池大小。将设备运行模式从联合立体声模式切换到双通道模式将几乎会让比特率翻倍，达到 617 kbps，这将在不改变比特池大小（也就是53）的情况下带来显著的音质提升。我个人觉得比特池大小应该是一个内部变量。我推测，由于 A2DP 规范自身的设计失误导致了比特池大小并非由其它变量决定，而成了一个独立可协商的参数。&lt;/p&gt;
&lt;h2 id="551-和-452-kbps-是哪来的"&gt;551 和 452 kbps 是哪来的？&lt;a class="headerlink" href="#551-和-452-kbps-是哪来的" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;蓝牙时分技术在设计上是能高效传输固定大小的大数据包的。数据传输以 &lt;strong&gt;槽&lt;/strong&gt; （slot） 为单位，而每次最多可传输 5 个槽。也有些传输模式使用 1 或 3 个槽，但从来没有用 2 或 4 的。在链接速率为 2 Mbps 的情况下，你可以用 5 个槽传输多达 679 字节数据，而在 3Mbps 的情况下可以传输至多 1021 字节的数据。当使用 3 个槽时，上述情况下的单次传输数据量分别为 367 和 552 字节。&lt;/p&gt;
&lt;p&gt;&lt;img alt="使用 5 slots 在 2 Mbps 情况下传输数据示意图" src="https://techie-s.work/posts/2023/01/lineage-bluetooth-sbc-xq/content_bluetooth_sbc_xq_3.png"/&gt;&lt;/p&gt;
&lt;p&gt;如果我们向传输少于 679 或 1021 字节但多于 367 或 552 字节的数据，单次传输依然要占用 5 个槽，而且传输会占用同样长的时间，也就降低了传输效率。&lt;/p&gt;
&lt;p&gt;&lt;img alt="44.1kHz 音频在 SBC 编码下的传输数据示意图" src="https://techie-s.work/posts/2023/01/lineage-bluetooth-sbc-xq/content_bluetooth_sbc_xq_4.png"/&gt;&lt;/p&gt;
&lt;p&gt;44100 Hz 的音频经由 SBC 在双通道模式、比特池大小 38、每帧 16 块、8 频带下编码将生成长度为 164 字节的音频数据帧，相应的比特率为 452 kb/s。&lt;/p&gt;
&lt;p&gt;&lt;img alt="帧长度计算公式" src="https://techie-s.work/posts/2023/01/lineage-bluetooth-sbc-xq/content_bluetooth_sbc_xq_1.png"/&gt;&lt;/p&gt;
&lt;p&gt;一个用尽 5 个槽的传输可以包含至多 4 个音频数据帧：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;679 (EDR 2 mbit/s DH5) - 4 (L2CAP) - 12 (AVDTP/RTP) - 1 (SBC header) - (164*4) = 6&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;单个数据包能至多传输 11.7 毫秒时长的音频数据，而这些数据将在 3.75 毫秒内传输完成，而我们还有 6 字节没用上。如果你稍稍提高一点比特池的大小，4 个音频数据帧将再也不能挤到单次传输之中。你不得不每次发送 3 帧，也就降低了传输效率、单次传输中的音频数据量，还会增加无线环境较差情况下的音频卡顿的概率。&lt;/p&gt;
&lt;p&gt;EDR 3 Mbps 情况下的 551 kbps 比特率也是用类似的方法选出的：在比特池大小为 47、每帧 16 块、8 个频带的情况下，帧大小为 200 字节，而比特率为 551 kbps。单词传输也就能用 5 个音频数据帧传输至多 14.6 毫秒的音乐。&lt;/p&gt;
&lt;p&gt;计算所有 SBC 可用参数的算法还挺复杂的，增加了出错的可能。如果你想自己全算一遍，建议使用这个 &lt;a href="https://btcodecs.valdikss.org.ru/sbc-bitrate-calculator"&gt;交互式计算器&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id="我们可以做得更好吗"&gt;我们可以做得更好吗？&lt;a class="headerlink" href="#我们可以做得更好吗" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;安卓的补丁包有一个额外选项可以增加 EDR 2Mbps 设备的比特率、超过它们既有的限制。你可以把比特率从 452 kbps 增加到 595 kbps，不过代价是当你的设备遇到拥塞的无线网络环境时传输稳定性会下降。&lt;/p&gt;
&lt;p&gt;为了启用上述功能，在安卓 root shell 中执行下面的命令：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# setprop persist.bluetooth.sbc_hd_higher_bitrate 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="这个改变真的有什么好处吗"&gt;这个改变真的有什么好处吗？&lt;a class="headerlink" href="#这个改变真的有什么好处吗" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;SoundExpert 的 Serge Smirnoff 做了一次 &lt;a href="http://soundexpert.org/articles/-/blogs/audio-quality-of-sbc-xq-bluetooth-audio-codec"&gt;蓝牙SBC XQ profile 测试&lt;/a&gt;。他的测试表明 SBC XQ 452 kbps 的音频比 aptX 的失真更少，而 551 kbps 的版本甚至可以和 aptX HD 比。&lt;/p&gt;
&lt;p&gt;想自己听听差异吗？试试这个实时 &lt;a href="https://btcodecs.valdikss.org.ru/sbc-encoder/"&gt;将音频转码为 SBC 的在线服务&lt;/a&gt;（也包括 aptX 和 aptX HD），就运行在浏览器里。你可以比较不同 SBC profile 和其它编码的不同，而不用真的去用蓝牙传输音频，只要用手边的有线耳机、音箱，然后上传你最喜欢的音乐就好。你也可以在播放过程中直接调整那些参数。&lt;/p&gt;
&lt;h2 id="结论"&gt;结论&lt;a class="headerlink" href="#结论" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;LineageOS 用户现在可以通过在他们的蓝牙设备设定里为“HD Audio: SBC”复选框打上勾，就能增强他们的蓝牙音频质量。这个特性应该能帮人们改变认为 SBC 低质量、应急用编码的这种偏见。当选择一个新的蓝牙耳机或音箱时，用户不再需要考虑那些私有编码了。&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:original-bluetooth-sbc-xq"&gt;
&lt;p&gt;https://www.lineageos.org/engineering/Bluetooth-SBC-XQ/ &lt;a class="footnote-backref" href="#fnref:original-bluetooth-sbc-xq" title="回到 #1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="Translations"/><category term="lineage"/><category term="bluetooth"/><category term="audio"/></entry><entry><title>HomeAssistant与ESP32</title><link href="https://techie-s.work/posts/2023/01/homeassistant-working-with-esp32/" rel="alternate"/><published>2023-01-12T00:00:00+08:00</published><updated>2023-01-12T00:00:00+08:00</updated><author><name/></author><id>tag:techie-s.work,2023-01-12:/posts/2023/01/homeassistant-working-with-esp32/</id><summary type="html">&lt;p&gt;因为突然对智能家居很感兴趣， &lt;del&gt;稍微&lt;/del&gt; 折腾了一下&lt;/p&gt;&lt;p&gt;Home Assistant是一个用Python实现的开源智能家居平台，着眼于不依赖于云服务的离线智能家居。有赖于社区动力，
主流智能 …&lt;/p&gt;</summary><content type="html">
&lt;h2 id="前言"&gt;前言&lt;a class="headerlink" href="#前言" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;因为突然对智能家居很感兴趣， &lt;del&gt;稍微&lt;/del&gt; 折腾了一下&lt;/p&gt;
&lt;h2 id="home-assistant"&gt;Home Assistant&lt;a class="headerlink" href="#home-assistant" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Home Assistant是一个用Python实现的开源智能家居平台，着眼于不依赖于云服务的离线智能家居。有赖于社区动力，
主流智能家居供应商的产品大多可接入Home Assistant管理。许多开源家居智能化方案也接入了Home Assistant。
就不过多介绍了。&lt;/p&gt;
&lt;h2 id="tasmota"&gt;Tasmota&lt;a class="headerlink" href="#tasmota" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="简介"&gt;简介&lt;a class="headerlink" href="#简介" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Tasmota是一个ESP32/ESP8266平台的开源固件，通过MQTT或HTTP API进行数据交互。
它将各个平台抽象为一系列模板，用统一的固件+硬件模板配置适配了各种各样的商业产品。
比如我手上的小米米家台灯1S，就可以刷成Tasmota固件。&lt;/p&gt;
&lt;p&gt;通过其内置的三种脚本系统（rules，scripts，berry scripts）可以实现很多复杂的功能，例如
联机自动注册设备、自动上报数据、自动触发某一长时间运行的流程。&lt;/p&gt;
&lt;p&gt;想在Home Assistant Core中接入Tasmota，只要自己搭建好MQTT服务器、启用MQTT和Tasmota集成，
再配置好Tasmota设备接入即可。&lt;/p&gt;
&lt;h3 id="实例tasmota人体传感器hass"&gt;实例：Tasmota+人体传感器+HASS&lt;a class="headerlink" href="#实例tasmota人体传感器hass" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;鉴于很多传感器都不需要多少配置就能无缝整合入Home Assistant，这里介绍一个需要一点操作的案例。&lt;/p&gt;
&lt;p&gt;人体传感器，简单版，参考下列资料&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;https://tasmota.github.io/docs/PIR-Motion-Sensors/&lt;/li&gt;
&lt;li&gt;https://tasmota.github.io/docs/Rules/&lt;/li&gt;
&lt;li&gt;https://www.home-assistant.io/integrations/mqtt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;步骤概要：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;配置Tasmota中检测信号的GPIO&lt;/li&gt;
&lt;li&gt;配置Tasmota中对应的自动化规则&lt;/li&gt;
&lt;li&gt;在Home Assistant中配置相关设备集成&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;首先是人体传感器信号检测。这里使用HLK-LD2410传感器，其支持已加入Tasmota 12.3.1，不过下面做的其实和所谓支持关系不大。
LD2410提供了一个OUT信号脚，在检测到人体时是高电平，一段时间未检测到人体后就变为低电平。将OUT与ESP32的某个GPIO
（下称GPIOx）相连，在Tasmota配置页面进入 &lt;code&gt;Configuration&lt;/code&gt; → &lt;code&gt;Configure Module&lt;/code&gt; ，将对应的GPIOx设置为Switch，并分配一个闲置ID。
下面假设这个Switch的ID是1。&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;Switch的ID无所谓，只要选空闲的就好。后面的指令需要将对应命令、Switch1等字串末尾的1改成对应ID。默认情况下，Switch会控制ID相同的灯。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;进入 &lt;code&gt;Consoles&lt;/code&gt; → &lt;code&gt;Console&lt;/code&gt; ，设置对应Switch模式，关闭其开关相同编号设备的作用，同时关闭开关对应的MQTT消息。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;SwitchMode1 1
SwitchTopic 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;给Tasmota配置一个自动化规则，在信号发生变化的时候发送MQTT消息，在连接到MQTT服务时向Home Assistant注册该传感器。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rule1
 on Switch1#state=1 do publish stat/%topic%/PIR1 ON endon
 on Switch1#state=0 do publish stat/%topic%/PIR1 OFF endon
 on mqtt#connected do publish2 homeassistant/binary_sensor/presence_%deviceid%/config { "name": "Presence Sensor", "object_id": "presence_%deviceid%", "device_class": "presence", "state_topic": "stat/%topic%/PIR1"} endon
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;rule说明&lt;/p&gt;
&lt;p&gt;rule有三个存储空间，分别为rule1、rule2、rule3。其中每个空间至少可以存储1000字节的指令，考虑到压缩，可能会更多。
rule1后面的1 &lt;strong&gt;不用&lt;/strong&gt; 和对应开关的ID一致！&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;在终端输入上述指令时注意合并成一行！&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;下面提供另外一种写法，不过根据配置不同，数据刷新可能不够及时&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="x"&gt;rule1&lt;/span&gt;
&lt;span class="x"&gt; on mqtt#connected do publish2 homeassistant/binary_sensor/presence_%deviceid%/config { "name": "Presence Sensor", "object_id": "presence_%deviceid%", "device_class": "presence", "state_topic": "tele/%topic%/SENSOR", "value_template": "&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;value_json.Switch1&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;"} endon&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;接下来启用rule1，保存配置并重启&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rule1 1
restart 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最后便可以在Home Assistant的实体注册表找到名为“Presence Sensor”的人体存在检测设备了。
通过复杂一点的脚本，还能实现人体动态与静态的检测，当然这就需要启用LD2410传感器对应的支持以利用串口数据了。&lt;/p&gt;
&lt;h2 id="esphome"&gt;ESPHome&lt;a class="headerlink" href="#esphome" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="架构介绍"&gt;架构介绍&lt;a class="headerlink" href="#架构介绍" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;就我目前所知，ESPHome的设计大致如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有ESPHome设备的固件由一组ESPHome程序（Dashboard或CLI）根据给出的设备配置（YAML）生成&lt;ul&gt;
&lt;li&gt;算是一揽子服务（配合PlatformIO CLI）&lt;/li&gt;
&lt;li&gt;初见比较难hack&lt;/li&gt;
&lt;li&gt;YAML解析有一些特殊语法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;每个ESPHome设备可以独立运行，不依赖面板/Dashboard&lt;ul&gt;
&lt;li&gt;面板只有UI/UX变了&lt;/li&gt;
&lt;li&gt;CLI有提供指引系统，但是支持的开发板数量较少，如果一开始就要使用不支持的开发板会比较困难&lt;/li&gt;
&lt;li&gt;固件可以通过USB安装，HTTPS连接dashboard的情况下可以用Web USB给ESP32编程&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Home Assistant通过约定的API接口与ESPHome设备交互数据，需要在设备配置YAML中写一行 &lt;code&gt;api:&lt;/code&gt; 开启API&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;（不得不说这个项目的文档很不适合我，很难检索到所需的资料）&lt;/p&gt;
&lt;p&gt;也就是说，ESPHome包括编译系统和边缘设备两部分。&lt;/p&gt;
&lt;h3 id="使用python虚拟环境准备esphome部署环境"&gt;使用Python虚拟环境准备ESPHome部署环境&lt;a class="headerlink" href="#使用python虚拟环境准备esphome部署环境" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;鉴于官网已经花费了大量篇幅从一开始就为Home Assistant和Docker用户介绍了对应的使用方法，这里将
完整地介绍一遍不使用Docker、Home Assistant插件来使用ESPHome的流程。&lt;/p&gt;
&lt;p&gt;前面说过，ESPHome把相关工作都包揽了，这里只需要构建一个虚拟环境作为工作空间就好。
构建虚拟环境可以使用下面的指令。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 创建虚拟环境&lt;/span&gt;
python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;venv&lt;span class="w"&gt; &lt;/span&gt;esphome-venv
&lt;span class="c1"&gt;# 激活虚拟环境&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;esphome-venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;虚拟环境的pypi镜像与代理配置&lt;/p&gt;
&lt;p&gt;可以直接在虚拟环境文件夹（上例中是 &lt;code&gt;esphome-venv&lt;/code&gt;）内新建一个pip.conf，在其中添加对应配置，例如使用阿里云的pypi源：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[global]&lt;/span&gt;
&lt;span class="na"&gt;index-url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;https://mirrors.aliyun.com/pypi/simple/&lt;/span&gt;

&lt;span class="k"&gt;[install]&lt;/span&gt;
&lt;span class="na"&gt;trusted-host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;mirrors.aliyun.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;接下来随意创建一个保存YAML的文件夹作为工作空间便可以了。&lt;/p&gt;
&lt;h3 id="实例使用luatos-core-esp32c3开发板制作低功耗蓝牙ble网关"&gt;实例：使用LuatOS-CORE-ESP32C3开发板制作低功耗蓝牙（BLE）网关&lt;a class="headerlink" href="#实例使用luatos-core-esp32c3开发板制作低功耗蓝牙ble网关" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;参考了下列资料：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;https://digiblur.com/wiki/ha/esphome-bluetooth-proxy-esp32c3/&lt;/li&gt;
&lt;li&gt;https://esphome.io/components/display/index.html&lt;/li&gt;
&lt;li&gt;https://esphome.io/components/display/st7735.html&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;激活虚拟环境，在你的配置保存文件夹内编写如下YAML，保存为 &lt;code&gt;ble-gateway.yaml&lt;/code&gt;。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;substitutions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;core-esp32c3-btproxy&lt;/span&gt;

&lt;span class="nt"&gt;esphome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;${display_name}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;platformio_options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;board_build.mcu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;esp32c3&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;board_build.variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;esp32c3&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;

&lt;span class="nt"&gt;esp32&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ESP32C3&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;esp32dev&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;framework&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;esp-idf&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;sdkconfig_options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;CONFIG_BT_BLE_50_FEATURES_SUPPORTED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;y&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;CONFIG_BT_BLE_42_FEATURES_SUPPORTED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;y&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;CONFIG_ESP_TASK_WDT_TIMEOUT_S&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;

&lt;span class="nt"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nt"&gt;ota&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;safe_mode&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;${display_name} (Safe Mode)&lt;/span&gt;

&lt;span class="c1"&gt;# 记得改WiFi和密码&lt;/span&gt;
&lt;span class="nt"&gt;wifi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Your&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;SSID"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Your&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secret&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;password"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;#manual_ip:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;#  static_ip: !secret ip_esp32c3_btproxy&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;#  gateway: !secret ip_gateway&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;#  subnet: !secret ip_subnet&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;#  dns1: !secret ip_dns1&lt;/span&gt;

&lt;span class="nt"&gt;esp32_ble_tracker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;scan_parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="c1"&gt;# 可能需要调参&lt;/span&gt;
&lt;span class="c1"&gt;#    interval: 1100ms&lt;/span&gt;
&lt;span class="c1"&gt;#    window: 1100ms&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;

&lt;span class="nt"&gt;bluetooth_proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;#### 下面是给显示器模块配置的 ###&lt;/span&gt;

&lt;span class="c1"&gt;# 五个按钮&lt;/span&gt;
&lt;span class="c1"&gt;# 由于 ESPHome 把 GPIO12、GPIO13强制占用了（原本控制Flash的），所以有一个按钮不能用。&lt;/span&gt;
&lt;span class="nt"&gt;binary_sensor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gpio&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Up&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Button"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;pin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;GPIO9&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;inverted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;pullup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gpio&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Down&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Button"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;pin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;GPIO5&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;inverted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;pullup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w w-Error"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gpio&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Left&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Button"&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;pin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;GPIO8&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nt"&gt;inverted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nt"&gt;pullup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="c1"&gt;#  - platform: gpio&lt;/span&gt;
&lt;span class="c1"&gt;#    name: "Right Button"&lt;/span&gt;
&lt;span class="c1"&gt;#    pin:&lt;/span&gt;
&lt;span class="c1"&gt;#      number: GPIO13 # this pin is spared on CORE-ESP32C3&lt;/span&gt;
&lt;span class="c1"&gt;#      inverted: true&lt;/span&gt;
&lt;span class="c1"&gt;#      mode:&lt;/span&gt;
&lt;span class="c1"&gt;#        input: true&lt;/span&gt;
&lt;span class="c1"&gt;#        pullup: true&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="w w-Error"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;gpio&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Center&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Button"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;pin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;GPIO4&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;inverted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;pullup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;# 显示器SPI&lt;/span&gt;
&lt;span class="nt"&gt;spi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;clk_pin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;GPIO2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;mosi_pin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;GPIO3&lt;/span&gt;

&lt;span class="c1"&gt;# 来点显示的东西&lt;/span&gt;
&lt;span class="c1"&gt;# 可惜写这个的时间貌似还没支持&lt;/span&gt;
&lt;span class="c1"&gt;#time:&lt;/span&gt;
&lt;span class="c1"&gt;#  - platform: sntp&lt;/span&gt;
&lt;span class="c1"&gt;#    id: sntp_time&lt;/span&gt;
&lt;span class="nt"&gt;font&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 字体需要自己拷贝一份哦，下面是相对于该配置文件的位置&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"fonts/iosevka-regular.ttf"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;my_font&lt;/span&gt;

&lt;span class="c1"&gt;# display ST7735&lt;/span&gt;
&lt;span class="nt"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;st7735&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"INITR_MINI160X80"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;reset_pin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;GPIO10&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;cs_pin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;GPIO7&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;dc_pin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;GPIO6&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;device_width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;device_height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;160&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;col_start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;row_start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;use_bgr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;|-&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;it.print(0, 1, id(my_font), "Hello");&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;it.print(0, 22, id(my_font), "World!");&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;// it.strftime(0, 35, id(my_font), "%Y-%m-%d %H:%M", id(sntp_time).now());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;接上设备，执行下面的命令刷机，就可以在Home Assistant里配置了。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;esphome&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;ble-gateway.yaml&lt;span class="w"&gt; &lt;/span&gt;--device&lt;span class="o"&gt;=&lt;/span&gt;/dev/ttyACM0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;Warning&lt;/p&gt;
&lt;p&gt;记得先激活虚拟环境！&lt;/p&gt;
&lt;/div&gt;</content><category term="Embedded"/><category term="esp32"/><category term="esp8266"/><category term="homeassistant"/></entry><entry><title>为CircuitPython编写专用的C功能模块</title><link href="https://techie-s.work/posts/2022/12/write-a-c-module-for-circuitpython/" rel="alternate"/><published>2022-12-03T00:00:00+08:00</published><updated>2022-12-03T00:00:00+08:00</updated><author><name/></author><id>tag:techie-s.work,2022-12-03:/posts/2022/12/write-a-c-module-for-circuitpython/</id><summary type="html">&lt;p&gt;自己的机械键盘需要一个高效、无阻塞的扫键模块，Makerdiary的既有实现不能满足我的需求。&lt;/p&gt;&lt;p&gt;通过参考 &lt;code&gt;keypad&lt;/code&gt;、&lt;code&gt;digitalio&lt;/code&gt; 以及Makerdiary的 &lt;code&gt;matrix&lt;/code&gt; 模块，我发现对于CircuitPython 7.x 有如下的代码结构 …&lt;/p&gt;</summary><content type="html">
&lt;h2 id="动机"&gt;动机&lt;a class="headerlink" href="#动机" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;自己的机械键盘需要一个高效、无阻塞的扫键模块，Makerdiary的既有实现不能满足我的需求。&lt;/p&gt;
&lt;h2 id="结构"&gt;结构&lt;a class="headerlink" href="#结构" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;通过参考 &lt;code&gt;keypad&lt;/code&gt;、&lt;code&gt;digitalio&lt;/code&gt; 以及Makerdiary的 &lt;code&gt;matrix&lt;/code&gt; 模块，我发现对于CircuitPython 7.x 有如下的代码结构安排：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;shared-bindings&lt;/code&gt;：存放模块Python接口的定义，和为Python开发C模块很像；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shared-module&lt;/code&gt;：实现与平台无关的模块存放其具体实现的地方；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ports/*/common-hal&lt;/code&gt;：实现与平台无关的模块存放其具体实现的地方；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ports/*/boards&lt;/code&gt;：某类平台的板极定义，可以用来调整特定板子的编译配置；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;py/circuitpy_defns.mk&lt;/code&gt;：大概是整体编译配置，通过添加内容让编译工具识别到添加的代码；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;py/circuitpy_mpconfig.mk&lt;/code&gt;：大概是整体编译选项配置，通过添加内容可以添加所需的选项开关；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那么实现某个模块的流程大致如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;确认Python接口，并将初步定义在 &lt;code&gt;shared-bindings&lt;/code&gt; 中创建好；&lt;/li&gt;
&lt;li&gt;确认该模块是否与平台强相关：&lt;ol&gt;
&lt;li&gt;若只需CircuitPython标准接口或其它模块暴露的接口，可以将实现放在 &lt;code&gt;shared-module&lt;/code&gt; 中；&lt;/li&gt;
&lt;li&gt;若确认实现与平台相关、用到了部分HAL提供的特殊方法，可以将实现放在对应的 &lt;code&gt;ports/*/common-hal&lt;/code&gt; 中；&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;实现模块具体细节；&lt;/li&gt;
&lt;li&gt;修改 &lt;code&gt;py/circuitpy_defns.mk&lt;/code&gt; 和 &lt;code&gt;py/circuitpy_mpconfig.mk&lt;/code&gt; 以添加对应模块的配置信息；&lt;/li&gt;
&lt;li&gt;修改 &lt;code&gt;ports/*/boards&lt;/code&gt; 中对应开发板的配置，开启所需模块；&lt;/li&gt;
&lt;li&gt;编译、测试与后续工作；&lt;/li&gt;
&lt;/ol&gt;</content><category term="Embedded"/><category term="circuitpython"/><category term="nrf52"/></entry><entry><title>Tinker Board 2S 折腾</title><link href="https://techie-s.work/posts/2022/11/hands-on-tinker-board-2s/" rel="alternate"/><published>2022-11-08T00:00:00+08:00</published><updated>2022-11-08T00:00:00+08:00</updated><author><name/></author><id>tag:techie-s.work,2022-11-08:/posts/2022/11/hands-on-tinker-board-2s/</id><summary type="html">&lt;p&gt;&lt;em&gt;&lt;strong&gt;WIP&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;这是出于某种原因从网友手上收来的一块板子，很漂亮，看着也很强悍。但是吧，官方支持的内核、RootFS什么的都挺老的（opinioned），实在满足不了我折 …&lt;/p&gt;</summary><content type="html">
&lt;p&gt;&lt;em&gt;&lt;strong&gt;WIP&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="tinkerboard2"&gt;TinkerBoard2&lt;a class="headerlink" href="#tinkerboard2" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这是出于某种原因从网友手上收来的一块板子，很漂亮，看着也很强悍。但是吧，官方支持的内核、RootFS什么的都挺老的（opinioned），实在满足不了我折腾的需求。&lt;/p&gt;
&lt;h2 id="相关文档与代码资料"&gt;相关文档与代码资料&lt;a class="headerlink" href="#相关文档与代码资料" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;除了使用说明 &lt;sup id="fnref:tb2-manual"&gt;&lt;a class="footnote-ref" href="#fn:tb2-manual"&gt;1&lt;/a&gt;&lt;/sup&gt;，还可以看一下 Schematics &lt;sup id="fnref:tb2-schematics"&gt;&lt;a class="footnote-ref" href="#fn:tb2-schematics"&gt;2&lt;/a&gt;&lt;/sup&gt;。Armbian &lt;sup id="fnref:armbian-build"&gt;&lt;a class="footnote-ref" href="#fn:armbian-build"&gt;3&lt;/a&gt;&lt;/sup&gt; 最近（2022-11-08）也有开发者在投入资源适配。&lt;/p&gt;
&lt;p&gt;这个板子的调试串口藏得真好……我直接忽略了 (藏在USB之间) 。&lt;/p&gt;
&lt;h2 id="研究路径"&gt;研究路径&lt;a class="headerlink" href="#研究路径" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;rk3399 启动流程 (官方wiki其它地方也能看看) &lt;ul&gt;
&lt;li&gt;http://opensource.rock-chips.com/wiki_Boot_option&lt;/li&gt;
&lt;li&gt;不能指望 Asus 能做什么, 除了他们那个系统 (TinkerOS) , 不过我是不用的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;一个不错的研究笔记&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/lanseyujie/tn3399_v3"&gt;TN3399_V3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;好歹都是 RK3399 的板子&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="原版-tinkeros-说明"&gt;原版 TinkerOS 说明&lt;a class="headerlink" href="#原版-tinkeros-说明" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="u-boot"&gt;U-Boot&lt;a class="headerlink" href="#u-boot" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Arm64 启动时涉及到 ATF (Arm Trusted Firmware), 这里先提一嘴, 也就是要注意下启动流程&lt;/p&gt;
&lt;p&gt;Tinker Board 2S 的 U-Boot 改自 Rockchip 维护的版本, &lt;strong&gt;默认调试接口的波特率是 115200&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;RK3399 优先从 eMMC 启动, 因此为了更方便地测试 uboot, 必须把 eMMC 上的 uboot 破
坏掉&lt;/p&gt;
&lt;p&gt;所以我在 uboot 里执行了下面的指令, 然后我就能愉快地从 SD 卡启动了。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 0x40 是 SPL/TPL（idbloader.img）的起址&lt;/span&gt;
mmc&lt;span class="w"&gt; &lt;/span&gt;erase&lt;span class="w"&gt; &lt;/span&gt;0x40&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="kernel"&gt;Kernel&lt;a class="headerlink" href="#kernel" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;版本 4.4 没得说, 毕竟 BSP 给的就是 4.4, &lt;s&gt;晚点给它干上去&lt;/s&gt;我做不到&lt;/p&gt;
&lt;h2 id="自行构建-u-bootkernelrootfs"&gt;自行构建 u-boot、kernel、rootfs&lt;a class="headerlink" href="#自行构建-u-bootkernelrootfs" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="atftf-a"&gt;ATF(TF-A)&lt;a class="headerlink" href="#atftf-a" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;一个初始化设备和EL2的程序（大概吧，不太懂）。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git clone https://github.com/ARM-software/arm-trusted-firmware.git
cd arm-trusted-firmware
make realclean
make CROSS_COMPILE=aarch64-linux-gnu- PLAT=rk3399
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="u-boot_1"&gt;U-Boot&lt;a class="headerlink" href="#u-boot_1" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;需要适配（写设备树、调整驱动）。看了下 Armbian 最近（2022-11-08）的commit，适配工作正在有序进行（而我只是勉强能明白他们的补丁想干嘛）。
u-boot版本为2022.07。&lt;/p&gt;
&lt;p&gt;目前（2022-11-08）看来，HDMI、USB都有点问题。通过修改配置，可以轻松地使用 SPI NOR Flash 存放 u-Boot，这样就不依赖
外置存储器了。&lt;/p&gt;
&lt;p&gt;如何配置暂时不记录。编译成功后生成适用于 flashrom 的脚本如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;span class="c1"&gt;# gen spi image&lt;/span&gt;
&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"tools/mkimage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"rk3399"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-T'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rkspi'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'tpl/u-boot-tpl.bin:spl/u-boot-spl.bin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'idbloader-spi.img'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="c1"&gt;# LOAD&lt;/span&gt;
&lt;span class="c1"&gt;## load spi tpl/spl&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'idbloader-spi.img'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rb'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tplspl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;## load itb&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u-boot.itb'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rb'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;uboot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;## CONCATENATE&lt;/span&gt;
&lt;span class="c1"&gt;### offset in KB&lt;/span&gt;
&lt;span class="n"&gt;FIT_OFFSET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;  &lt;span class="c1"&gt;# 这里请根据设备树配置项目 `u-boot,spl-payload-offset' 和 `CONFIG_SYS_SPI_FLASH_U_BOOT_OFFS' 等配置进行修改&lt;/span&gt;
&lt;span class="n"&gt;FLASH_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8192&lt;/span&gt;  &lt;span class="c1"&gt;# Flash 大小，只提供rom数据的情况下，flashrom要求rom数据和rom大小一致。&lt;/span&gt;
&lt;span class="n"&gt;FIT_OFFSET_BYTES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FIT_OFFSET&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;
&lt;span class="n"&gt;FLASH_SIZE_BYTES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FLASH_SIZE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tplspl&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0xff&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FIT_OFFSET_BYTES&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tplspl&lt;/span&gt;&lt;span class="p"&gt;))])&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;uboot&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mh"&gt;0xff&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FLASH_SIZE_BYTES&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uboot&lt;/span&gt;&lt;span class="p"&gt;))])&lt;/span&gt;
&lt;span class="c1"&gt;## WRITE&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"spi-nor.bin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wb'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;## FLASH, check your permission&lt;/span&gt;
&lt;span class="c1"&gt;# subprocess.run(['flashrom', '-p', 'ch341a_spi', '-c', "MX25L6406E/MX25L6408E" '-w', 'spi-nor.bin'])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="rootfs"&gt;rootfs&lt;a class="headerlink" href="#rootfs" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;没啥难度，对于Debian/Ubuntu 有 debootstrap，对于 ArchLinux 有 ArchLinuxARM，不同发行版的RootFS有很多对应制作工具。略。&lt;/p&gt;
&lt;h3 id="kernel_1"&gt;kernel&lt;a class="headerlink" href="#kernel_1" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;没搞定，旧的能用继续用旧的，略。&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:tb2-manual"&gt;
&lt;p&gt;https://tinker-board.asus.com/images/doc/download/E17169_Tinker_Board_2_2S_UM_WEB.pdf &lt;a class="footnote-backref" href="#fnref:tb2-manual" title="回到 #1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:tb2-schematics"&gt;
&lt;p&gt;https://tinker-board.asus.com/images/doc/download/Tinker_Board_2_Schematics_20201214.pdf &lt;a class="footnote-backref" href="#fnref:tb2-schematics" title="回到 #2"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:armbian-build"&gt;
&lt;p&gt;https://github.com/armbian/build &lt;a class="footnote-backref" href="#fnref:armbian-build" title="回到 #3"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="Embedded"/><category term="rk3399"/><category term="asus"/><category term="tinker-board"/><category term="arm"/></entry><entry><title>3D打印机折腾 - XYZPrinting da Vinci 1.0</title><link href="https://techie-s.work/posts/2022/10/hack-on-xyzprinting-davinci-10/" rel="alternate"/><published>2022-10-30T00:00:00+08:00</published><updated>2022-11-10T00:00:00+08:00</updated><author><name/></author><id>tag:techie-s.work,2022-10-30:/posts/2022/10/hack-on-xyzprinting-davinci-10/</id><summary type="html">&lt;p&gt;&lt;img alt="最终修改完成的机器" src="https://techie-s.work/posts/2022/10/hack-on-xyzprinting-davinci-10/PIC-01-final-result.jpg"/&gt;&lt;/p&gt;&lt;p&gt;因为某种缘分，我上了一个库存3D打印机的车。买到的打印机是 XYZPrinting 出品的 da Vinci 系列，版本1.0，下面简称达芬奇。现在这个系列都加上了 …&lt;/p&gt;</summary><content type="html">
&lt;p&gt;&lt;img alt="最终修改完成的机器" src="https://techie-s.work/posts/2022/10/hack-on-xyzprinting-davinci-10/PIC-01-final-result.jpg"/&gt;&lt;/p&gt;
&lt;h2 id="前言"&gt;前言&lt;a class="headerlink" href="#前言" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;因为某种缘分，我上了一个库存3D打印机的车。买到的打印机是 XYZPrinting 出品的 da Vinci 系列，版本1.0，下面简称达芬奇。现在这个系列都加上了前缀“original”。&lt;/p&gt;
&lt;p&gt;该打印机用的是私有协议，而我不喜欢被私有上位机控制的感觉（GNU/Linux上操作、配合其它软件时会很麻烦）。于是折腾了“一下下”。&lt;/p&gt;
&lt;p&gt;幸运的是我拿到的版本比较老，可以更换电机驱动器。&lt;/p&gt;
&lt;h2 id="机器上手"&gt;机器上手&lt;a class="headerlink" href="#机器上手" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这个设备真的比想象的大不少……明明工作空间只有&lt;code&gt;200*200*200&lt;/code&gt;的说……占地差不多 &lt;code&gt;500*600&lt;/code&gt;，高度 &lt;code&gt;500&lt;/code&gt;，单位都是毫米。&lt;/p&gt;
&lt;p&gt;比较可惜的是，设备到我手上时已经因运输不当损坏了多个部件，例如柜门转轴、上翻盖转轴、热床支架、热床玻璃、Z轴底座等等。总之先尝试调平、打印了内置测试件，感觉还行。&lt;/p&gt;
&lt;p&gt;&lt;img alt="打印机内部" src="https://techie-s.work/posts/2022/10/hack-on-xyzprinting-davinci-10/PIC-01-inside-shell.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;可惜这个调平不是长久之计，网上搜索后没有找到合适的热床组件，找商家补发了一个备件。在此期间先研究一下通信协议、耗材限制吧。&lt;/p&gt;
&lt;p&gt;在此先记录一下我对于3D打印机到手后的操作建议：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;检查机械结构&lt;/li&gt;
&lt;li&gt;上电检查状况&lt;/li&gt;
&lt;li&gt;检查是否校准并视情况调整（例如使用自动校准功能）&lt;/li&gt;
&lt;li&gt;装料&lt;/li&gt;
&lt;li&gt;打印测试&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="耗材锁与上位机协议"&gt;耗材锁与上位机协议&lt;a class="headerlink" href="#耗材锁与上位机协议" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我机器随附光盘的程序没法正常运行……网上找到的官方程序也没法成功地在虚拟机里打印…… &lt;del&gt;可惜了&lt;/del&gt; 那正好，必须折腾起来。好在网友留下了大量研究资料&lt;sup id="fnref:filament-reseter"&gt;&lt;a class="footnote-ref" href="#fn:filament-reseter"&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;sup id="fnref:minimover-doc"&gt;&lt;a class="footnote-ref" href="#fn:minimover-doc"&gt;2&lt;/a&gt;&lt;/sup&gt;（相关论坛资料主要发布于2016年前后，很久远，说明这机器年岁也很高）。&lt;/p&gt;
&lt;p&gt;“简单”看了一下，就是用特定串口命令上传文件呗，用Py快速实现了一个小工具&lt;sup id="fnref:hyx0329-davinci-utils"&gt;&lt;a class="footnote-ref" href="#fn:hyx0329-davinci-utils"&gt;3&lt;/a&gt;&lt;/sup&gt;。
第一次上传gcode时一个不小心让打印头撞了一下平台……这好像也给后面打印立方体测试时埋下了隐患，导致模型某个侧面疯狂突出。&lt;/p&gt;
&lt;p&gt;用这个方法打印模型需要用USB连接串口。需要注意的是，没调整好的话床很容易撞喷头，具体我就不详细写了。&lt;/p&gt;
&lt;h2 id="刷机-repetier-固件"&gt;刷机 - Repetier 固件&lt;a class="headerlink" href="#刷机-repetier-固件" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="主板照片，2013年末的板子" src="https://techie-s.work/posts/2022/10/hack-on-xyzprinting-davinci-10/PIC-01-mainboard.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;工具写到一半发现有个现成的开源固件。刷机也很方便，直接短接、清除原固件后就可以直接用 Arduino IDE 或 PlatformIO IDE 上传。但是在使用过程中会遇到一些小问题。
中间虽然尝试了备份原固件，但发现JTAG接口并不能用=_=，算了反正原装固件是枷锁。&lt;/p&gt;
&lt;p&gt;我的刷机过程如下，其它设备会略有不同，请参考固件说明。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;检查型号兼容性&lt;/li&gt;
&lt;li&gt;断电-&amp;gt;短接JP1-&amp;gt;上电-&amp;gt;过几秒断电-&amp;gt;断开JP1&lt;/li&gt;
&lt;li&gt;Clone并打开项目文件夹，修改 &lt;code&gt;src/ArduinoDUE/Repetier/Configuration.h&lt;/code&gt; 中的配置&lt;/li&gt;
&lt;li&gt;PlatformIO IDE 编译、上传，随后进行设备校准与检测&lt;/li&gt;
&lt;li&gt;根据实际设备情况调整参数&lt;/li&gt;
&lt;li&gt;重新编译、上传，校准与检测&lt;/li&gt;
&lt;li&gt;终于测试通过了，正常使用&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我的挤出头边上的探针（probe）长度与测试点位置的位置好像和程序预定义的有点不同，在使用过程中发现了一些问题。不得不修改了一下代码。&lt;/p&gt;
&lt;h2 id="加装wifi模块"&gt;加装WiFi模块&lt;a class="headerlink" href="#加装wifi模块" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;ESP3D这个东西还是有点意思的。哦，装了这个玩意儿可能会让USB串口不正常。&lt;/p&gt;
&lt;p&gt;以 ESP12F 为例，2.1.x 固件的使用注意点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;del&gt;供电得足……&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;esp3d/config.h&lt;/code&gt; 中对你需要的功能进行调整&lt;/li&gt;
&lt;li&gt;刷机前最好用esptool把模块的存储器擦除一遍，以清除所有会影响到的配置&lt;/li&gt;
&lt;li&gt;刷机后上机工作不太正常是正常的，需要简单配置一下&lt;ul&gt;
&lt;li&gt;连接热点后上传index.html.gz，找不到可以fzf/find一下&lt;/li&gt;
&lt;li&gt;走一遍tour，配置工作模式&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2.x成熟一点，指引直观易用；3.0更先进，但是不知道什么时候才有稳定版，没什么指引不太方便&lt;/li&gt;
&lt;li&gt;打印机固件也要开启WiFi功能哦&lt;/li&gt;
&lt;li&gt;Repetier for davinci 最大波特率只有 500000，如果你调高了，ESP3D和打印机固件会自动将波特率降下来&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;硬件连接参考如下（达芬奇1.0 跳线JP1版），可以先看看原项目的示意图&lt;sup id="fnref:esp3d-wiring-davinci"&gt;&lt;a class="footnote-ref" href="#fn:esp3d-wiring-davinci"&gt;4&lt;/a&gt;&lt;/sup&gt;。&lt;strong&gt;原项目建议串口加一对分压电阻把RX电平降下来&lt;/strong&gt;。
该串口的波特率和 USB 串口的波特率（打印机设置的）一样。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;+---------+        +---------------+
|     GND |___  ___|1 to RX        |
|         |   \/   |               |
|     TXD |_  /\  _|2 to VCC(3.3V) |
| ESP     | \/  \/ |               |
|     RXD |_/\  /\_|3 to GND       |
|         |   \/   |               |
|     VCC |___/\___|4 to TX        |
+---------+        +---------------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;测试的时候要同时面对好几个疑问真的头疼死了……总之装上去了！&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;Warning&lt;/p&gt;
&lt;p&gt;后续升级达芬奇固件时，一定把ESP3D取下来！不然会有影响！&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="将ESP12F自制小板安装在达芬奇内部" src="https://techie-s.work/posts/2022/10/hack-on-xyzprinting-davinci-10/PIC-01-install-wifi-module.jpg"/&gt;&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;关于项目仓库提供的连接示意图&lt;/p&gt;
&lt;p&gt;ESP3D项目地址中达芬奇1.0/2.0的连接示意图标注的是连接方法，不是引脚定义。即RX接RX，TX接TX。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition attention"&gt;
&lt;p class="admonition-title"&gt;达芬奇装WiFi模块供电&lt;/p&gt;
&lt;p&gt;不要用LDO（或者说不要用低效率的LDO），原本供电刚好 &lt;strong&gt;3.3V&lt;/strong&gt; 且足够ESP8266开热点，过一个LDO可能就不够用了。如果版型和我的一样，串口可以直接对接，电平都是3.3V不会烧。&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="换电机驱动"&gt;换电机驱动&lt;a class="headerlink" href="#换电机驱动" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;想用TMS2209换掉原来的A4988。达芬奇的电机驱动板排针间距大概7个单位距离，比常见的5个单位距离宽一些（1单位距离为2.54mm，排针间距），需要改造。
目前想到的办法有魔改双层转接器和自制转接板。结果咧，转接器（双层排母但是两层一样）都做好了，但是怕操作失误把板子弄坏，还是放弃了（网友称其‘fragile’）。
&lt;del&gt;以后有机会用自制转接板改吧，感觉稳妥些。&lt;/del&gt; 事实证明 &lt;del&gt;直接直连转接的话需要改一下电机插头线序&lt;/del&gt; 只是方向相反而已，另外做的转接板线接错了直接报废。
另外说一句，我搞不太懂这些步进电机的1A2B什么的编号，好像有两种标准很坑人（一种字母对应线圈/相、数字对应正负，另一种反过来）。
只要知道接对了电机会动，接错了电机会原地抖动，估计就没啥问题了。&lt;/p&gt;
&lt;p&gt;&lt;img alt="魔改的双排母，其实差一点就很合适了" src="https://techie-s.work/posts/2022/10/hack-on-xyzprinting-davinci-10/PIC-01-custom-connector.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;del&gt;嘉立创免费打样了一组转接板，配合长排针，把原来的母座改了，同时兼容原版和常见的TMC2209&lt;/del&gt; 总之就是直接报废。电机驱动根据soliforum的网友提供的信息&lt;sup id="fnref:step-motor-info"&gt;&lt;a class="footnote-ref" href="#fn:step-motor-info"&gt;5&lt;/a&gt;&lt;/sup&gt;来看，
驱动电流不应该超过1A，大概在0.8A左右合适。100mΩ的情况下调整电压差不多1V应该就行了。&lt;/p&gt;
&lt;p&gt;提前预留了活动空间并测试了一下电机驱动方向，发现恰好相反，那么稍微修改一下固件把它矫正过来～&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// 需要修改的内容如下，给出的是使用TMC2209时的值&lt;/span&gt;
&lt;span class="cp"&gt;#define INVERT_X_DIR false&lt;/span&gt;
&lt;span class="cp"&gt;#define INVERT_Y_DIR true&lt;/span&gt;
&lt;span class="cp"&gt;#define INVERT_Z_DIR false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;继续用原来的长排针，发现单位移动距离好像对，可能是微步设置出现问题，不管三七二十一先重新做了“可靠的”转换器，又实验了好几次，非常不稳定，中间还不小心插反了一个模块（-11CNY (ಥ_ಥ)），只剩3个可用了……
一筹莫展之际，想到了板子MS1、MS2、MS3是没有隔离的，而且TMC2209的MS3是个TX（输出模式的IO），说不定影响到了微步设置？嗯，咱把对应的引脚剪掉吧！还好只是剪转接器的引脚。oh，还真的是这样！一切终于正常了。&lt;/p&gt;
&lt;p&gt;&lt;img alt="电机驱动模块与转接器及引脚裁剪示意图" src="https://techie-s.work/posts/2022/10/hack-on-xyzprinting-davinci-10/PIC-01-tmc2209-module.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;用TMC2209替换A4988的感受嘛，声音确实小了很多，还是挺明显的。在打印精度上基本没有多少提升=_=。总体上确实有提升就是啦。&lt;/p&gt;
&lt;h2 id="关于校准"&gt;关于校准&lt;a class="headerlink" href="#关于校准" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;平台平衡校准，配合纸片手动调平比根据自动测量数据调整要靠谱得多（说明自动测量是废物/当然也不完全是）。&lt;/p&gt;
&lt;p&gt;我建议的校准流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;平台校准&lt;ol&gt;
&lt;li&gt;清理平台和打印头，这一步在手动校准程序启动时会有个选项；&lt;/li&gt;
&lt;li&gt;手动校准（Manual leveling），如果觉得不放心可以多来两次练练手（按照固件提示操作走完流程就行）;&lt;/li&gt;
&lt;li&gt;重新检测Zmin（Zmin evaluation）；&lt;/li&gt;
&lt;li&gt;打印测试件验证校准状况。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;挤出机校准&lt;ol&gt;
&lt;li&gt;在耗材上做好长度标记，然后按程序挤出一段耗材（比如100mm），测量实际挤出长度；&lt;/li&gt;
&lt;li&gt;然后嘛就是一个比例计算， &lt;span class="math"&gt;\(S_{new} = S_{old} \frac{E_{program}}{E_{actual}}\)&lt;/span&gt;；&lt;/li&gt;
&lt;li&gt;也可以参考一下网上资料 &lt;sup id="fnref:steps-per-mm-calculator"&gt;&lt;a class="footnote-ref" href="#fn:steps-per-mm-calculator"&gt;10&lt;/a&gt;&lt;/sup&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="挤出头与热端更换"&gt;挤出头与热端更换&lt;a class="headerlink" href="#挤出头与热端更换" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;目前看到有网友换了 MK8 或 E3D 的热端&lt;sup id="fnref:davinci-mk8-hotend"&gt;&lt;a class="footnote-ref" href="#fn:davinci-mk8-hotend"&gt;6&lt;/a&gt;&lt;/sup&gt; &lt;sup id="fnref:davinci-hotend-discussions"&gt;&lt;a class="footnote-ref" href="#fn:davinci-hotend-discussions"&gt;7&lt;/a&gt;&lt;/sup&gt;。以后我得尝试一下。热端拆解得在网上找视频了。看起来以后可以直接换MK8热端。&lt;/p&gt;
&lt;h2 id="温度传感器更换"&gt;温度传感器更换&lt;a class="headerlink" href="#温度传感器更换" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;参考开源固件讨论区&lt;sup id="fnref:davinci-repetier-change-thermistor"&gt;&lt;a class="footnote-ref" href="#fn:davinci-repetier-change-thermistor"&gt;8&lt;/a&gt;&lt;/sup&gt;，有两种做法：一是改主板并更换传感器从而能用既有的通用数据，二是自己测对应的温度数据。&lt;/p&gt;
&lt;h2 id="其他人的改造"&gt;其他人的改造&lt;a class="headerlink" href="#其他人的改造" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;发现有人直接把主板换掉魔改了一番&lt;sup id="fnref:frankenvinci"&gt;&lt;a class="footnote-ref" href="#fn:frankenvinci"&gt;9&lt;/a&gt;&lt;/sup&gt;。我暂时没这个动力……先好好用用看吧，要换也先换热端和电机驱动了。&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:filament-reseter"&gt;
&lt;p&gt;&lt;a href="https://github.com/voltivo/davinci_filament_reset_arduino"&gt;耗材重置器（仅Arduino AVR测试可用）&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:filament-reseter" title="回到 #1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:minimover-doc"&gt;
&lt;p&gt;&lt;a href="https://github.com/reality-boy/miniMover/tree/master/docs"&gt;miniMover项目文档，包括部分协议文档&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:minimover-doc" title="回到 #2"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:hyx0329-davinci-utils"&gt;
&lt;p&gt;&lt;a href="https://github.com/hyx0329/xyzdavinci-3d-printer-utils"&gt;达芬奇小工具（但是不会继续开发了）&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:hyx0329-davinci-utils" title="回到 #3"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:esp3d-wiring-davinci"&gt;
&lt;p&gt;&lt;a href="https://github.com/luc-github/ESP3D/wiki/Hardware-connection#davinci-1020-board"&gt;ESP3D硬件连接-davinci1.0/2.0&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:esp3d-wiring-davinci" title="回到 #4"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:step-motor-info"&gt;
&lt;p&gt;&lt;a href="https://www.soliforum.com/post/121940/#p121940"&gt;步进电机型号信息来源&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:step-motor-info" title="回到 #5"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:davinci-mk8-hotend"&gt;
&lt;p&gt;&lt;a href="https://www.instructables.com/How-to-Modify-a-XYZ-Da-Vinci-3D-Printer-to-Work-Wi/"&gt;达芬奇改装MK8热端–Milen（Instructables）&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:davinci-mk8-hotend" title="回到 #6"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:davinci-hotend-discussions"&gt;
&lt;p&gt;&lt;a href="https://www.thingiverse.com/groups/da-vinci/forums/general/topic:7799"&gt;一些关于达芬奇热端更换的讨论&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:davinci-hotend-discussions" title="回到 #7"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:davinci-repetier-change-thermistor"&gt;
&lt;p&gt;&lt;a href="https://github.com/luc-github/Repetier-Firmware-4-Davinci/discussions/104"&gt;达芬奇使用第三方温度传感器&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:davinci-repetier-change-thermistor" title="回到 #8"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:frankenvinci"&gt;
&lt;p&gt;&lt;a href="https://www.techmonkeybusiness.com/articles/Frankenvinci.html"&gt;弗兰肯芬奇（魔改达芬奇）–Frankenvinci&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:frankenvinci" title="回到 #9"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:steps-per-mm-calculator"&gt;
&lt;p&gt;https://www.maxzprint.com.au/stepps-per-mm-calculator/ &lt;a class="footnote-backref" href="#fnref:steps-per-mm-calculator" title="回到 #10"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:repetier4davinci"&gt;
&lt;p&gt;&lt;a href="https://github.com/luc-github/Repetier-Firmware-4-Davinci"&gt;Repetier-Firmware-4-Davinci&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:repetier4davinci" title="回到 #11"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="Projects"/><category term="3d-printing"/><category term="esp8266"/><category term="esp32"/><category term="esp3d"/><category term="arduino"/><category term="platformio"/></entry><entry><title>芒果派RISCV：麻雀 MQ Pro 上手体验</title><link href="https://techie-s.work/posts/2022/09/riscv-mangopi-mq-pro/" rel="alternate"/><published>2022-09-11T00:00:00+08:00</published><updated>2022-09-11T00:00:00+08:00</updated><author><name/></author><id>tag:techie-s.work,2022-09-11:/posts/2022/09/riscv-mangopi-mq-pro/</id><summary type="html">&lt;p&gt;这是一块以全志D1（RISCV）CPU为核心的迷你开发板，外观规格同树莓派0（不过USB全改
Type-C了，好评）。特性列出如下：&lt;/p&gt;&lt;p&gt;粉色油墨、过孔盖油、小尺寸、RISCV、开源、该有的接口 …&lt;/p&gt;</summary><content type="html">
&lt;h2 id="芒果派-mq-pro-简介"&gt;芒果派 MQ Pro 简介&lt;a class="headerlink" href="#芒果派-mq-pro-简介" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这是一块以全志D1（RISCV）CPU为核心的迷你开发板，外观规格同树莓派0（不过USB全改
Type-C了，好评）。特性列出如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU：Allwinner D1 (RISCV single core)&lt;/li&gt;
&lt;li&gt;Form factor: Raspi-0-like&lt;/li&gt;
&lt;li&gt;Peripheral ports: GPIO, I2C &amp;amp; SPI, SDIO, Audio port(record, play),
  Video(touch, HDMI, DVP, DSI and LVDS), USB Host, OTG, etc. &lt;/li&gt;
&lt;li&gt;Connectivity: WiFi/BT(RTL8723ds)&lt;/li&gt;
&lt;li&gt;Memory: 512MB/1GB respectively for MPi-MQ1PL/MPi-MQ1PH&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;粉色油墨、过孔盖油、小尺寸、RISCV、开源、该有的接口都有了，除了性能弱一些，其实
还不错。&lt;/p&gt;
&lt;h2 id="自制钥匙串"&gt;自制“钥匙串”&lt;a class="headerlink" href="#自制钥匙串" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;嗯因为板子比较小也好看，不想焊接排针，于是非常具有装饰性，做个钥匙串不过分吧？
我用 FreeCAD 按树莓派0的规格绘制了简单的亚克力外壳，用不锈钢齐平螺母和平头螺
丝、尼龙隔离柱和垫片等将开发板夹在了两块亚克力板中间，亚克力板则是做成钥匙扣的
样子。&lt;/p&gt;
&lt;p&gt;&lt;img alt="将开发板像三明治一样夹在两块亚克力板之间，做成钥匙扣，整体成长方形，但一条短边像弦一样被拉出形成一个三角形孔洞，另一条短边有一个恰好能用于取出MicroSD卡的椭圆形凹陷，钥匙扣四角用螺丝固定" src="https://techie-s.work/posts/2022/09/riscv-mangopi-mq-pro/riscv-mangopi-mq-pro-keychain.jpg"/&gt;&lt;/p&gt;
&lt;h2 id="简单玩玩安装-archlinux"&gt;简单玩玩，安装 ArchLinux&lt;a class="headerlink" href="#简单玩玩安装-archlinux" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="社区力量"&gt;社区力量&lt;a class="headerlink" href="#社区力量" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;嗯？好像没有那么简单啊……不过感谢社区的力量&lt;sup id="fnref:rv-img-builder"&gt;&lt;a class="footnote-ref" href="#fn:rv-img-builder"&gt;1&lt;/a&gt;&lt;/sup&gt;！我成功安装了 ArchLinux。&lt;/p&gt;
&lt;h3 id="进一步折腾"&gt;进一步折腾&lt;a class="headerlink" href="#进一步折腾" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;del&gt;闲着无聊&lt;/del&gt; 将 sehraf 的脚本用 Makefile 重新实现了一遍&lt;sup id="fnref:rv-img-builder-makefile"&gt;&lt;a class="footnote-ref" href="#fn:rv-img-builder-makefile"&gt;2&lt;/a&gt;&lt;/sup&gt;，顺便修了一些bug，都自
动化了。前前后后花了三四天吧。全当学 Makefile 了。&lt;/p&gt;
&lt;h2 id="接着玩"&gt;接着玩？&lt;a class="headerlink" href="#接着玩" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;emm，暂时没有想法。不过它确实是我的 tiny-tiny-tiny card computer。&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:rv-img-builder"&gt;
&lt;p&gt;&lt;a href="https://github.com/sehraf/riscv-arch-image-builder"&gt;riscv-arch-image-builder by sehraf&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:rv-img-builder" title="回到 #1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:rv-img-builder-makefile"&gt;
&lt;p&gt;&lt;a href="https://github.com/hyx0329/riscv-archlinux-d1"&gt;Makefile 实现的 D1 平台 ArchLinux 镜像构建工具&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:rv-img-builder-makefile" title="回到 #2"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="Projects"/><category term="riscv"/><category term="archlinux"/><category term="mangopi"/></entry><entry><title>在MSM8916随身WiFi上玩转Linux - part2 新设备</title><link href="https://techie-s.work/posts/2022/08/openstick-msm8916-part2-new-device/" rel="alternate"/><published>2022-08-07T00:00:00+08:00</published><updated>2022-09-11T00:00:00+08:00</updated><author><name/></author><id>tag:techie-s.work,2022-08-07:/posts/2022/08/openstick-msm8916-part2-new-device/</id><summary type="html">&lt;p&gt;哎呀，剁手了，又买了一个随身WiFi想刷机。不过这次不再是 &lt;code&gt;UFI001C_MB_V01&lt;/code&gt; 而是
&lt;code&gt;UFI_16_V3&lt;/code&gt;。刷机流程稍有不同。后面又整了台 &lt;code&gt;UFI003&lt;/code&gt;，看过发现和 &lt;code&gt;UFI001C&lt;/code&gt; 一样。&lt;/p&gt;&lt;p&gt;首先是 …&lt;/p&gt;</summary><content type="html">
&lt;h2 id="动机"&gt;动机&lt;a class="headerlink" href="#动机" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;哎呀，剁手了，又买了一个随身WiFi想刷机。不过这次不再是 &lt;code&gt;UFI001C_MB_V01&lt;/code&gt; 而是
&lt;code&gt;UFI_16_V3&lt;/code&gt;。刷机流程稍有不同。后面又整了台 &lt;code&gt;UFI003&lt;/code&gt;，看过发现和 &lt;code&gt;UFI001C&lt;/code&gt; 一样。&lt;/p&gt;
&lt;h2 id="刷机流程"&gt;刷机流程&lt;a class="headerlink" href="#刷机流程" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="备份"&gt;备份&lt;a class="headerlink" href="#备份" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;首先是 &lt;strong&gt;备份备份备份&lt;/strong&gt;！&lt;/p&gt;
&lt;p&gt;比较友好的是，该款版型的系统原生开启高通调试接口，备份基带QCN无需ROOT。老规矩，上
&lt;strong&gt;QPST&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;其次是全盘eMMC备份。这里使用 &lt;strong&gt;Qualcomm Premium Tool&lt;/strong&gt; 中备份Block0的功能。&lt;/p&gt;
&lt;h3 id="fastboot刷机"&gt;fastboot刷机&lt;a class="headerlink" href="#fastboot刷机" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;下面尝试进入fastboot刷机。哎？怎么刷不进去？&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;% fastboot flash partition gpt_both0.bin 
Sending 'partition' (33 KB)                        OKAY [  0.005s]
Writing 'partition'                                FAILED (remote: 'failed to write partition,please contact vendor')
fastboot: error: Command failed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;看样子fastboot做了限制加了锁。经过一番研究，发现在MSM8916平台上fastboot在 &lt;code&gt;aboot&lt;/code&gt;（appsboot/lk1st） 分
区，而刷GNU/Linux正好也要更新这个分区，干脆直接用 &lt;strong&gt;Qualcomm Premium Tool&lt;/strong&gt; 把
该写的 &lt;code&gt;aboot&lt;/code&gt; 分区通过9008提前写入。看上去不错，fastboot被替换掉了。&lt;/p&gt;
&lt;p&gt;然后看情况稍微修改一下 Debian 的刷机脚本，不过好像不改也行。总之就是自己准备好备份，然后相应刷就行。&lt;/p&gt;
&lt;p&gt;接下来的步骤和前一篇文章基本一致。&lt;/p&gt;
&lt;h2 id="补充"&gt;补充&lt;a class="headerlink" href="#补充" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="led怎么不亮"&gt;LED怎么不亮&lt;a class="headerlink" href="#led怎么不亮" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;del&gt;emm这块板子的LED是用 &lt;code&gt;aw2013&lt;/code&gt; 控制的，是 &lt;code&gt;i2c6&lt;/code&gt; 下面挂的一颗LED控制器。需要相应地修改设备树。&lt;/del&gt; 
我完完全全地被骗了，这就是三个普通的GPIO LED，只不过其中有一个直接连到了PMIC
上。另外，这板子上的红灯居然是常亮的，只和电源是否接通有关系（看板子估计是因为用一个电阻替换了应有的三脚管）；蓝色LED结果是个绿
的，我猜都是为了省经费。&lt;/p&gt;
&lt;h3 id="apt-key-的代替方案"&gt;apt-key 的代替方案&lt;a class="headerlink" href="#apt-key-的代替方案" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;read https://www.linuxuprising.com/2021/01/apt-key-is-deprecated-how-to-add.html&lt;/p&gt;
&lt;h3 id="修改appsboot"&gt;修改appsboot&lt;a class="headerlink" href="#修改appsboot" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Clone lk2nd，修改相关配置并编译。原来的aboot是给 &lt;code&gt;UFI001C_MB_V01&lt;/code&gt; 用的，这里hack一下给 &lt;code&gt;UFI_16_V3&lt;/code&gt; 用。其实也就
修改了一下相关LED的引脚定义，顺带改一下product名称，没别的了。&lt;/p&gt;
&lt;h2 id="后记"&gt;后记&lt;a class="headerlink" href="#后记" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;经过这次曲折的研究，我意外学习了一些设备树和驱动的概念。感觉设备树这个设计很神
奇啊，本来分散的硬件通过设备树的描述就可以当成一个整体了；驱动集成到内核也把硬
件细节封装了起来；原本的芯片像是通过外围设备组成了一个更大的芯片。以后看情况再
补充学习一下。&lt;/p&gt;</content><category term="Projects"/><category term="qcom"/><category term="msm8916"/><category term="linux"/></entry><entry><title>WeAct BlackPill 跑 CircuitPython 使用自选外部 Flash</title><link href="https://techie-s.work/posts/2022/08/weact-blackpill-run-circuitpython-with-custom-external-flash/" rel="alternate"/><published>2022-08-05T00:00:00+08:00</published><updated>2022-08-05T00:00:00+08:00</updated><author><name/></author><id>tag:techie-s.work,2022-08-05:/posts/2022/08/weact-blackpill-run-circuitpython-with-custom-external-flash/</id><summary type="html">&lt;p&gt;就是想用便宜的Flash。另外之前在CircuitPython 6.x时代也修改过了，但是之前买的拆机
Flash 质量实在太差，当U盘很不稳，不得已从库存的新Flash中挑了一片 &lt;code&gt;MX25L6406EM2I&lt;/code&gt;。
&lt;del&gt;才发现某创商城 …&lt;/del&gt;&lt;/p&gt;</summary><content type="html">&lt;h2 id="动机"&gt;动机&lt;a class="headerlink" href="#动机" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;就是想用便宜的Flash。另外之前在CircuitPython 6.x时代也修改过了，但是之前买的拆机
Flash 质量实在太差，当U盘很不稳，不得已从库存的新Flash中挑了一片 &lt;code&gt;MX25L6406EM2I&lt;/code&gt;。
&lt;del&gt;才发现某创商城搞活动时卖的 Flash 是停产/EOL产品。&lt;/del&gt;&lt;/p&gt;
&lt;h2 id="修改"&gt;修改&lt;a class="headerlink" href="#修改" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这个很容易，关键是要知道修改哪里。在CPY 6.x时代，所有的Flash配置都在一个个
结构体中（存疑，可能记错了），驱动大部分是可以通用的。而在CPY 7.x时代，所有的Flash
都有一个自己的 &lt;code&gt;toml&lt;/code&gt; 配置文件，定义了其中的各个参数。&lt;/p&gt;
&lt;p&gt;准备一份源码。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/adafruit/circuitpython.git
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;circuitpython
make&lt;span class="w"&gt; &lt;/span&gt;fetch-submodule
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;CPY 7.x的Flash配置文件在 &lt;code&gt;data/nvm.toml&lt;/code&gt; 子模块下。以我的Flash为例，依葫芦
（&lt;code&gt;MX25L1606&lt;/code&gt;，&lt;code&gt;MX25L3206E&lt;/code&gt;）画瓢，新建
&lt;code&gt;data/nvm.toml/flash/macronix/MX25L6406E.toml&lt;/code&gt;。另外我严重怀疑 &lt;code&gt;MX25L1606&lt;/code&gt; 的最
大时钟频率配置错了，少了个0。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Settings for the Macronix MX25L6406E 8MiB SPI flash.&lt;/span&gt;
&lt;span class="c1"&gt;# Datasheet: https://www.macronix.com/Lists/Datasheet/Attachments/8630/MX25L6406E,%203V,%2064Mb,%20v1.9.pdf&lt;/span&gt;
&lt;span class="n"&gt;total_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x800000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 8 MiB&lt;/span&gt;
&lt;span class="n"&gt;start_up_time_us&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;
&lt;span class="n"&gt;manufacturer_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xc2&lt;/span&gt;
&lt;span class="n"&gt;memory_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x20&lt;/span&gt;
&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x17&lt;/span&gt;
&lt;span class="n"&gt;max_clock_speed_mhz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;
&lt;span class="n"&gt;quad_enable_bit_mask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x40&lt;/span&gt;
&lt;span class="n"&gt;has_sector_protection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;然后修改对应板子的配置文件。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/ports/stm/boards/stm32f411ce_blackpill_with_flash/mpconfigboard.mk b/ports/stm/boards/stm32f411ce_blackpill_with_flash/mpconfigboard.mk&lt;/span&gt;
&lt;span class="gh"&gt;index 9da716374..bf048775e 100644&lt;/span&gt;
&lt;span class="gd"&gt;--- a/ports/stm/boards/stm32f411ce_blackpill_with_flash/mpconfigboard.mk&lt;/span&gt;
&lt;span class="gi"&gt;+++ b/ports/stm/boards/stm32f411ce_blackpill_with_flash/mpconfigboard.mk&lt;/span&gt;
&lt;span class="gu"&gt;@@ -5,7 +5,7 @@ USB_MANUFACTURER = &amp;quot;WeAct&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;SPI_FLASH_FILESYSTEM = 1
&lt;span class="w"&gt; &lt;/span&gt;#See supervisor/shared/external_flash/devices.h for options
&lt;span class="gd"&gt;-EXTERNAL_FLASH_DEVICES = GD25Q16C,W25Q64FV,W25Q32JVxQ,W25Q64JVxQ&lt;/span&gt;
&lt;span class="gi"&gt;+EXTERNAL_FLASH_DEVICES = GD25Q16C,W25Q64FV,W25Q32JVxQ,W25Q64JVxQ,MX25L6406E&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;LONGINT_IMPL = MPZ

&lt;span class="w"&gt; &lt;/span&gt;INTERNAL_FLASH_FILESYSTEM = 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;再之后编译。&lt;del&gt;不得不提这编译的速度貌似比NRF52快多了。&lt;/del&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;make&lt;span class="w"&gt; &lt;/span&gt;-C&lt;span class="w"&gt; &lt;/span&gt;mpy-cross
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ports/stm
make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;BOARD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;stm32f411ce_blackpill_with_flash&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="k"&gt;$(&lt;/span&gt;nproc&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;最后刷固件。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;dfu-util&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 列出当前的 DFU 设备&lt;/span&gt;
dfu-util&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-D&lt;span class="w"&gt; &lt;/span&gt;build_stm32f411ce_blackpill_with_flash/firmware.bin&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;0x08000000:leave
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Very easy，不是么～&lt;/p&gt;
&lt;h2 id="结语"&gt;结语&lt;a class="headerlink" href="#结语" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;好像也没什么好说的。啊对，珍爱开发板，远离拆机Flash。&lt;/p&gt;</content><category term="Embedded"/><category term="circuitpython"/><category term="stm32"/></entry><entry><title>在高通骁龙410主控的USB网卡上玩 GNU/Linux</title><link href="https://techie-s.work/posts/2022/07/openstick-msm8916/" rel="alternate"/><published>2022-07-29T00:00:00+08:00</published><updated>2022-09-13T00:00:00+08:00</updated><author><name/></author><id>tag:techie-s.work,2022-07-29:/posts/2022/07/openstick-msm8916/</id><summary type="html">&lt;p&gt;有段时间网上基于物联网卡的USB式移动数据终端（4G网卡）变多了，搞开车活动，设备很
便宜，价格10块上下，而且默认可以用 ADB 和 Fastboot。作为骁龙410，内核主线 …&lt;/p&gt;</summary><content type="html">
&lt;h2 id="动机"&gt;动机&lt;a class="headerlink" href="#动机" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;有段时间网上基于物联网卡的USB式移动数据终端（4G网卡）变多了，搞开车活动，设备很
便宜，价格10块上下，而且默认可以用 ADB 和 Fastboot。作为骁龙410，内核主线支持也
不错。是一个可玩性很高的小设备。我买了某品牌的设备，板号是 &lt;code&gt;UFI001C_MB_V01&lt;/code&gt;，目前已经
有网友搞定了，可以跑主线内核的 Debian Bullseye。&lt;/p&gt;
&lt;p&gt;后面又入了一个 &lt;code&gt;UFI003&lt;/code&gt;，结构基本一致。&lt;/p&gt;
&lt;p&gt;&lt;s&gt; &lt;em&gt;这东西好像可以拿来做下载机、签到、旁路由、渗透工具&lt;/em&gt; &lt;/s&gt;&lt;/p&gt;
&lt;h2 id="目前可以公开的信息"&gt;目前可以公开的信息&lt;a class="headerlink" href="#目前可以公开的信息" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;设备信息（&lt;code&gt;UFI001C_MB_V01&lt;/code&gt;）&lt;ul&gt;
&lt;li&gt;CPU：MSM8916，Snapdragon 410，A53@4C，64bit&lt;/li&gt;
&lt;li&gt;RAM/Storage：512M/4G，实际可用约382M/3.3G（因为Modem占用了一些内存）&lt;/li&gt;
&lt;li&gt;无线：802.11b/g/n，4G LTE，Bluetooth 4.1，其它SoC支持的也不一定能
  用，因为没有接外设出来&lt;/li&gt;
&lt;li&gt;有线：USB（可模拟网卡设备，同时用于供电）&lt;/li&gt;
&lt;li&gt;其它说明&lt;ul&gt;
&lt;li&gt;板上有一颗 eSIM，应该是互联网卡注册用的。&lt;/li&gt;
&lt;li&gt;有一个 SIM 卡槽位置，但是没有焊接卡座&lt;/li&gt;
&lt;li&gt;有一颗按钮，按住它上电会进入 EDL/9008 模式。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;操作系统&lt;ul&gt;
&lt;li&gt;原版系统：安卓（没看版本，盲猜魔改 AOSP&lt;/li&gt;
&lt;li&gt;修改系统：Debian（融合部分Mobian）、OpenWrt，以及其它发行版&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;刷机方式&lt;ul&gt;
&lt;li&gt;原版系统开放 ADB、Fastboot&lt;/li&gt;
&lt;li&gt;9008工具亦可&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="刷机流程"&gt;刷机流程&lt;a class="headerlink" href="#刷机流程" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="备份"&gt;备份&lt;a class="headerlink" href="#备份" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;div class="admonition danger"&gt;
&lt;p class="admonition-title"&gt;刷机警告&lt;/p&gt;
&lt;p&gt;道路千万条， &lt;strong&gt;备份&lt;/strong&gt; 第一条。&lt;br/&gt;
刷机太心急，锁网废机器。  &lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;先使用9008工具做全盘备份，ROOT之后用QPST备份基带（QCN）&lt;/strong&gt;，然后再刷系统。&lt;/p&gt;
&lt;h4 id="9008-全盘备份"&gt;9008 全盘备份&lt;a class="headerlink" href="#9008-全盘备份" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;9008刷机用 Windows 方便一些。为了能使用高通测试签名的驱动，需要使用命令行开启测试模式。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# 设置后需重启以应用&lt;/span&gt;
&lt;span class="n"&gt;bcdedit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exe&lt;/span&gt; &lt;span class="n"&gt;-set&lt;/span&gt; &lt;span class="n"&gt;testsigning&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;
&lt;span class="c"&gt;# 刷完后用下面这条改回去&lt;/span&gt;
&lt;span class="n"&gt;bcdedit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exe&lt;/span&gt; &lt;span class="n"&gt;-set&lt;/span&gt; &lt;span class="n"&gt;testsigning&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;使用 &lt;strong&gt;Qualcomm Premium Tool&lt;/strong&gt; 做完整的 eMMC 备份。需要使用 Windows。
做全盘备份请按图上顺序点击，在出现分区选择时选择最后一个分区（这样备份从0到最后
一个分区），后续按提示选择合适的位置存放全盘镜像。只需要保留这一份全盘备份就
行，其它分区、分区表等信息可以从中提取。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Qualcomm Premium Tool 操作提示，先进入qualcomm分页下的partitions分页，再按Read(9008)" src="https://techie-s.work/posts/2022/07/openstick-msm8916/qcom-tool.PNG"/&gt;&lt;/p&gt;
&lt;h4 id="基带备份"&gt;基带备份&lt;a class="headerlink" href="#基带备份" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;基带/QCN 备份，需要在安卓系统下开启高通的调试接口然后用 QPST 提取。基带芯片是一
个协处理器，其固件数据存储在一片 NVRAM 中，所以不能直接用 9008 模式读取。&lt;/p&gt;
&lt;p&gt;Root有很多方法，在此不详叙，仅提一些操作要点。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果要用桌面投屏控制这个棒子，请先在投屏成功后再安装桌面应用并将其设置为默认
  桌面，否则会一直黑；&lt;/li&gt;
&lt;li&gt;好像没了&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;开启测试端口的方法如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;adb&lt;span class="w"&gt; &lt;/span&gt;shell
&lt;span class="c1"&gt;# 接下来是在 adb shell 中操作&lt;/span&gt;
su
&lt;span class="c1"&gt;# 注意留住下面的 adb，不然adb就暂时用不了了&lt;/span&gt;
setprop&lt;span class="w"&gt; &lt;/span&gt;sys.usb.config&lt;span class="w"&gt; &lt;/span&gt;diag,adb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;接下来就可以使用 QPST 的 Software Download 程序，在 Backup 分页中操作并读取当前
设备的基带固件了（记得选好自己的设备）。&lt;/p&gt;
&lt;p&gt;另外串号什么的最好也备份一下。我这次有点浮躁忍不了 Windows（甩锅）。&lt;/p&gt;
&lt;p&gt;&lt;del&gt;驱动的事Windows好像并不是全都比GNU/Linux那边方便，感觉 Linux 那边通常更方便。&lt;/del&gt;&lt;/p&gt;
&lt;h3 id="debian-安装与使用"&gt;Debian 安装与使用&lt;a class="headerlink" href="#debian-安装与使用" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;Warning&lt;/p&gt;
&lt;p&gt;OpenStick 项目及其主页提供的部分内容有误，且 RootFS 配置上有 Bug，导致会出
现 USB 网络和 WiFi 热点不能同时开启的现象。如果有能力，建议自行重建 RootFS。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;刷 Debian 可以参考 OpenStick 的文档 &lt;sup id="fnref:openstick-flash-debian"&gt;&lt;a class="footnote-ref" href="#fn:openstick-flash-debian"&gt;3&lt;/a&gt;&lt;/sup&gt;。此处略过。&lt;br/&gt;
不过 Modem 固件（运行时加载的固件）文档并没有写清，在一个 64MB 大小的分区里，所
有的固件都在一个 images 文件夹里。部分软件提取出来的分区镜像名是 &lt;code&gt;NON-HLOS.bin&lt;/code&gt;。&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;emm OpenStick小队整的 Debian RootFS 好像缺了不少常用的包，而且配置好像有点点小问题。
记录一下操作。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;更新时间 &lt;code&gt;timedatectl set-ntp true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;安装编辑器：vim&lt;/li&gt;
&lt;li&gt;安装 curl/wget/ca-certificates 保证 https 连接&lt;/li&gt;
&lt;li&gt;换源到 USTC (网易163源貌似缺包，&lt;code&gt;ftp.cn.debian.org&lt;/code&gt; 又是直接重定向到 USTC 会有证书错误)&lt;/li&gt;
&lt;li&gt;安装 Bluez 并加载相关模块而启用蓝牙&lt;/li&gt;
&lt;li&gt;安装 Zsh/OhMyZsh 方便操作&lt;/li&gt;
&lt;li&gt;安装 iproute2/iw 等网络工具&lt;/li&gt;
&lt;li&gt;更新到 Debian Bookworm&lt;/li&gt;
&lt;li&gt;&lt;del&gt;安装 fake-hwclock 保证时间一致性&lt;/del&gt; 不用了，可以用 systemd-timesyncd&lt;/li&gt;
&lt;li&gt;写了个脚本做网络连接 failsafe，后面有写&lt;/li&gt;
&lt;li&gt;改了设备树，让按钮可以用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;iperf3 测了一下速度，2.4G WiFi 速度有 33Mbps，USB 则有 112 Mbps。Modem 暂时没条
件测试。&lt;/p&gt;
&lt;h3 id="编译与修改内核"&gt;编译与修改内核&lt;a class="headerlink" href="#编译与修改内核" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;本节内容有参考 OpenStick 文档&lt;sup id="fnref:openstick-compile-kernel"&gt;&lt;a class="footnote-ref" href="#fn:openstick-compile-kernel"&gt;1&lt;/a&gt;&lt;/sup&gt;，并加入了自己的更新。&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;Warning&lt;/p&gt;
&lt;p&gt;以下内容仅供参考，不保证准确可用，请灵活应变！&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;如果你用的是非 Debian 系的 GNU/Linux，建议创建一个 Chroot。BTW 我电脑用的是
Arch Linux，后面自行构建了一个 Debian bookworm RootFS，在 Chroot 内操作。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Chroot on arch - optional approach&lt;/span&gt;
mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/home/user/CHROOT/bookworm-cache
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/home/user/CHROOT/
sudo&lt;span class="w"&gt; &lt;/span&gt;debootstrap&lt;span class="w"&gt; &lt;/span&gt;--arch&lt;span class="o"&gt;=&lt;/span&gt;amd64&lt;span class="w"&gt; &lt;/span&gt;--cache-dir&lt;span class="w"&gt; &lt;/span&gt;/home/user/CHROOT/bookworm-cache&lt;span class="w"&gt; &lt;/span&gt;--foreign&lt;span class="w"&gt; &lt;/span&gt;bookworm&lt;span class="w"&gt; &lt;/span&gt;bookworm&lt;span class="w"&gt; &lt;/span&gt;http://mirrors.ustc.edu.cn/debian/
sudo&lt;span class="w"&gt; &lt;/span&gt;arch-chroot&lt;span class="w"&gt; &lt;/span&gt;bookworm
/debootstrap/debootstrap&lt;span class="w"&gt; &lt;/span&gt;--second-stage
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后准备环境、制作 &lt;code&gt;boot.img&lt;/code&gt;。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Debian bookworm environment&lt;/span&gt;
apt&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;build-essential&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;gcc-aarch64-linux-gnu&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;bc&lt;span class="w"&gt; &lt;/span&gt;bison&lt;span class="w"&gt; &lt;/span&gt;cpio&lt;span class="w"&gt; &lt;/span&gt;flex&lt;span class="w"&gt; &lt;/span&gt;rsync&lt;span class="w"&gt; &lt;/span&gt;kmod&lt;span class="w"&gt; &lt;/span&gt;libssl-dev

&lt;span class="c1"&gt;# build kernel&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;--depth&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git@github.com:OpenStick/linux.git&lt;span class="w"&gt; &lt;/span&gt;linux-openstick
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;linux-openstick
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm64
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CROSS_COMPILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aarch64-linux-gnu-
make&lt;span class="w"&gt; &lt;/span&gt;msm8916_defconfig
make&lt;span class="w"&gt; &lt;/span&gt;deb-pkg&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# packages are generated in the parent directory&lt;/span&gt;

&lt;span class="c1"&gt;## prepare data for boot.img&lt;/span&gt;
mkdir&lt;span class="w"&gt; &lt;/span&gt;../android-boot
cp&lt;span class="w"&gt; &lt;/span&gt;arch/arm64/boot/dts/qcom/msm8916-handsome-openstick-ufi001c.dtb&lt;span class="w"&gt; &lt;/span&gt;../android-boot
cp&lt;span class="w"&gt; &lt;/span&gt;arch/arm64/boot/Image.gz&lt;span class="w"&gt; &lt;/span&gt;../android-boot

&lt;span class="c1"&gt;# install kernel packages&lt;/span&gt;
scp&lt;span class="w"&gt; &lt;/span&gt;../linux-headers-5.15.0-handsomekernel_5.15.0-handsomekernel-1_arm64.deb&lt;span class="w"&gt; &lt;/span&gt;user@openstick.local:
scp&lt;span class="w"&gt; &lt;/span&gt;../linux-image-5.15.0-handsomekernel_5.15.0-handsomekernel-1_arm64.deb&lt;span class="w"&gt; &lt;/span&gt;user@openstick.local:
ssh&lt;span class="w"&gt; &lt;/span&gt;user@openstick.local&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;remove&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linux-image-5.15.0-handsomekernel+"&lt;/span&gt;
ssh&lt;span class="w"&gt; &lt;/span&gt;user@openstick.local&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;remove&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linux-headers-5.15.0-handsomekernel+"&lt;/span&gt;
ssh&lt;span class="w"&gt; &lt;/span&gt;user@openstick.local&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;dpkg&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;linux-image-5.15.0-handsomekernel_5.15.0-handsomekernel-1_arm64.deb&lt;span class="w"&gt; &lt;/span&gt;linux-headers-5.15.0-handsomekernel_5.15.0-handsomekernel-1_arm64.deb

&lt;span class="c1"&gt;# get initrd.img&lt;/span&gt;
scp&lt;span class="w"&gt; &lt;/span&gt;user@openstick.local:/boot/initrd.img-5.15.0-handsomekernel&lt;span class="w"&gt; &lt;/span&gt;../android-boot

&lt;span class="c1"&gt;# generate boot.img&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;../android-boot
cat&lt;span class="w"&gt; &lt;/span&gt;Image.gz&lt;span class="w"&gt; &lt;/span&gt;msm8916-handsome-openstick-ufi001c.dtb&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;kernel-dtb
mkbootimg&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--base&lt;span class="w"&gt; &lt;/span&gt;0x80000000&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--kernel_offset&lt;span class="w"&gt; &lt;/span&gt;0x00080000&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--ramdisk_offset&lt;span class="w"&gt; &lt;/span&gt;0x02000000&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--tags_offset&lt;span class="w"&gt; &lt;/span&gt;0x01e00000&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--pagesize&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--second_offset&lt;span class="w"&gt; &lt;/span&gt;0x00f00000&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--ramdisk&lt;span class="w"&gt; &lt;/span&gt;initrd.img-5.15.0-handsomekernel&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--cmdline&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"earlycon root=PARTUUID=a7ab80e8-e9d1-e8cd-f157-93f69b1d141e console=ttyMSM0,115200 no_framebuffer=true rw"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--kernel&lt;span class="w"&gt; &lt;/span&gt;kernel-dtb&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;boot.img

&lt;span class="c1"&gt;# flash boot.img&lt;/span&gt;
adb&lt;span class="w"&gt; &lt;/span&gt;reboot&lt;span class="w"&gt; &lt;/span&gt;bootloader
fastboot&lt;span class="w"&gt; &lt;/span&gt;flash&lt;span class="w"&gt; &lt;/span&gt;boot&lt;span class="w"&gt; &lt;/span&gt;boot.img
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;截止文章发布，该内核设备树的按钮电平配置还是反的，我已经改了，不多说了。&lt;/p&gt;
&lt;p&gt;按钮修好后，可以配置 systemd-logind 设置 &lt;code&gt;HandleRebootKey&lt;/code&gt; 和
&lt;code&gt;HandleRebootKeyLongPress&lt;/code&gt; 为 &lt;code&gt;ignore&lt;/code&gt;，然后用别的程序监控这个按钮发出别的操
作。不过也可以用 &lt;code&gt;systemd-inhibit&lt;/code&gt; 去 &lt;code&gt;handle-reboot-key&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="其它说明"&gt;其它说明&lt;a class="headerlink" href="#其它说明" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;这个设备 sim 卡热插拔不太稳的样子。所以尽量别热插拔了可能 Modem 会出现运行错
  误；&lt;/li&gt;
&lt;li&gt;内核默认配置是激活外部 eSIM，所以自己插卡后需要手动切卡（通过SysFs写点东
  西），然后可能需要重启 ModemManager，之后大概就能获取到网络信息了。至少我获取
  到了。&lt;/li&gt;
&lt;li&gt;&lt;s&gt;我也不知道全网通基带考不靠谱，虽然我刷了，但是没测试（这个测试起来太麻烦
  了）&lt;/s&gt;，后面感觉并不靠谱，没必要；&lt;/li&gt;
&lt;li&gt;&lt;del&gt;想用按钮，或许要自己改 device tree&lt;/del&gt;；&lt;/li&gt;
&lt;li&gt;这东西，Modem 发热严重，可以试试改用低功耗模式；另，使用不匹配的固件同样会造
  成 Modem 发热严重。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="有趣的应用"&gt;有趣的应用&lt;a class="headerlink" href="#有趣的应用" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="先让自己的设备断网也不怕"&gt;先让自己的设备断网也不怕&lt;a class="headerlink" href="#先让自己的设备断网也不怕" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;虽说可以用串口终端，不过也不是什么时候都方便。所以我写了一些脚本&lt;sup id="fnref:openstick-failsafe-guard"&gt;&lt;a class="footnote-ref" href="#fn:openstick-failsafe-guard"&gt;2&lt;/a&gt;&lt;/sup&gt;帮我根据情况恢复网络。&lt;/p&gt;
&lt;p&gt;其中一个服务会在网络断开时尝试启用所有的候选网络，开启恢复用的接入点；另一个则
是监控仅有的 GPIO 按钮，并根据用户配置执行对应操作。&lt;/p&gt;
&lt;p&gt;感谢来自下面这些链接中内容的作者们，他们给了我很多启发：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;https://github.com/beagleboard/linux/issues/20&lt;/li&gt;
&lt;li&gt;https://unix.stackexchange.com/questions/25372/turn-off-buffering-in-pipe&lt;/li&gt;
&lt;li&gt;https://unix.stackexchange.com/questions/428399/how-can-i-run-a-shell-script-on-input-device-event&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="添加-sim-卡槽"&gt;添加 SIM 卡槽&lt;a class="headerlink" href="#添加-sim-卡槽" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;网上很多把原来的 eSIM 干掉的做法，emm 实际上没必要。焊接卡槽可以用精密的尖头烙
铁。我用 936 就把 SIM 卡槽焊接好了。如图所示。
&lt;s&gt;跳线是测试连通性时发现 GND 居然不通，所以额外跳了一根线到最近的测试点上（CPU
的金属罩子它不沾锡啊TvT）。&lt;/s&gt;
实际上不用跳线，因为这个卡槽的GND就是板子设计的使能控制脚，接地可能就只能用卡槽
了，会影响esim切卡。后来我把这根线剪掉了。&lt;/p&gt;
&lt;p&gt;&lt;img alt="添加 SIM 卡槽从而能插自己的卡" src="https://techie-s.work/posts/2022/07/openstick-msm8916/dongle-add-sim-holder.jpg"/&gt;&lt;/p&gt;
&lt;h3 id="切换-sim-卡"&gt;切换 SIM 卡&lt;a class="headerlink" href="#切换-sim-卡" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;该设备的 SIM 卡使用 GPIO 作为切换开关，但切换后需要重启 modem 以应用配置。下面
列出操作示例。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 先设置激活你卡对应的led/gpio开关，比如说&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/sys/class/leds/esim0_select/brightness&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# 禁用 esim0&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/sys/class/leds/sim0_select/brightness&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# 启用 sim0&lt;/span&gt;
&lt;span class="c1"&gt;# 重新加载内核模块并重启相关服务&lt;/span&gt;
modprobe&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;qcom-q6v5-mss
modprobe&lt;span class="w"&gt; &lt;/span&gt;qcom-q6v5-mss
systemctl&lt;span class="w"&gt; &lt;/span&gt;restart&lt;span class="w"&gt; &lt;/span&gt;rmtfs
systemctl&lt;span class="w"&gt; &lt;/span&gt;restart&lt;span class="w"&gt; &lt;/span&gt;ModemManager
&lt;span class="c1"&gt;# 注意，有些设备的modem在使用跨运营商卡的时候会主动崩溃一次&lt;/span&gt;
&lt;span class="c1"&gt;# 此时视情况需要重启ModemManager&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="迷你短信平台"&gt;迷你短信平台&lt;a class="headerlink" href="#迷你短信平台" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;咕咕咕&lt;/p&gt;
&lt;h3 id="home-assistant"&gt;Home Assistant&lt;a class="headerlink" href="#home-assistant" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;咕咕咕&lt;/p&gt;
&lt;p&gt;测试了，太吃内存，就算禁用modem空内存出来还是不够用，还会令WiFi性能急剧下降。&lt;/p&gt;
&lt;h2 id="结语"&gt;结语&lt;a class="headerlink" href="#结语" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;emm懒了，这里的内容晚点看情况随缘更新（咕咕咕）&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:openstick-compile-kernel"&gt;
&lt;p&gt;&lt;a href="https://www.kancloud.cn/handsomehacker/openstick/2637565"&gt;OpenStick – 编译内核&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:openstick-compile-kernel" title="回到 #1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:openstick-failsafe-guard"&gt;
&lt;p&gt;&lt;a href="https://github.com/hyx0329/openstick-failsafe-guard"&gt;github/hyx0329 - Openstick Failsafe Guard&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:openstick-failsafe-guard" title="回到 #2"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:openstick-flash-debian"&gt;
&lt;p&gt;&lt;a href="https://www.kancloud.cn/handsomehacker/openstick/2636506"&gt;OpenStick - 开始折腾&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:openstick-flash-debian" title="回到 #3"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:open-stick-doc"&gt;
&lt;p&gt;&lt;a href="https://www.kancloud.cn/handsomehacker/openstick/2636505"&gt;关于 OpenStick - 概述&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:open-stick-doc" title="回到 #4"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:universal-adb-drivers"&gt;
&lt;p&gt;&lt;a href="https://adb.clockworkmod.com/"&gt;Universal ADB Drivers - ClockWorkMod&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:universal-adb-drivers" title="回到 #5"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="Projects"/><category term="qcom"/><category term="msm8916"/><category term="linux"/></entry><entry><title>M60 Keyboard - 运行 Python 的机械键盘</title><link href="https://techie-s.work/posts/2022/07/circuitpython-notes-000/" rel="alternate"/><published>2022-07-03T00:00:00+08:00</published><updated>2023-06-09T00:00:00+08:00</updated><author><name/></author><id>tag:techie-s.work,2022-07-03:/posts/2022/07/circuitpython-notes-000/</id><summary type="html">&lt;p&gt;折腾这个主要是因为我自己有用一个固件是 CircuitPython&lt;sup id="fnref:circuitpython"&gt;&lt;a class="footnote-ref" href="#fn:circuitpython"&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;sup id="fnref:circuitpython-git"&gt;&lt;a class="footnote-ref" href="#fn:circuitpython-git"&gt;2&lt;/a&gt;&lt;/sup&gt; 的机械键盘&lt;sup id="fnref:pykb-makerdiary"&gt;&lt;a class="footnote-ref" href="#fn:pykb-makerdiary"&gt;3&lt;/a&gt;&lt;/sup&gt;。
然后官方好像咕咕咕了很久没更新，我就忍不了了（也 …&lt;/p&gt;</summary><content type="html">
&lt;h2 id="背景"&gt;背景&lt;a class="headerlink" href="#背景" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;折腾这个主要是因为我自己有用一个固件是 CircuitPython&lt;sup id="fnref:circuitpython"&gt;&lt;a class="footnote-ref" href="#fn:circuitpython"&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;sup id="fnref:circuitpython-git"&gt;&lt;a class="footnote-ref" href="#fn:circuitpython-git"&gt;2&lt;/a&gt;&lt;/sup&gt; 的机械键盘&lt;sup id="fnref:pykb-makerdiary"&gt;&lt;a class="footnote-ref" href="#fn:pykb-makerdiary"&gt;3&lt;/a&gt;&lt;/sup&gt;。
然后官方好像咕咕咕了很久没更新，我就忍不了了（也正好有空），直接自己上，把固件
大版本提了上来（6.0.0 -&amp;gt; 7.3.1），以便使用更多特性。&lt;/p&gt;
&lt;h2 id="定制-circuitpython"&gt;定制 CircuitPython&lt;a class="headerlink" href="#定制-circuitpython" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;为什么不用官方发行并支持的二进制版本呢？emm主要是官方固件不接USB就会停机,也就不能无线使用了，只能
定制一下固件。&lt;/p&gt;
&lt;p&gt;在 GitHub 上对比了 Makerdiary 和 Adafruit 官方版两个分支的不同
&lt;sup id="fnref:compare-makerdiary-adafruit"&gt;&lt;a class="footnote-ref" href="#fn:compare-makerdiary-adafruit"&gt;4&lt;/a&gt;&lt;/sup&gt;，从中剥离了 &lt;code&gt;matrix&lt;/code&gt; 模块和针对键盘的定制，调整
成了一组 Patch，调整后的补丁区分度更高，也方便后面继续升级版本。下面简单说一下
遇到的困难。&lt;/p&gt;
&lt;h3 id="未声明的函数"&gt;未声明的函数&lt;a class="headerlink" href="#未声明的函数" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;对于 CircuitPython 7.x 的编译系统，所有用到的函数都需要声明。几乎没写过靠谱的C的我，在面对
这个问题的时候竟然非常小心，总认为光声明没有用。&lt;/p&gt;
&lt;p&gt;然而事实是，补全声明就行了。&lt;/p&gt;
&lt;h3 id="不匹配的类型"&gt;不匹配的类型&lt;a class="headerlink" href="#不匹配的类型" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;CircuitPython 7.x 开始，跟上了 MicroPython 内置 C 模块的接口设计，不再用旧的那
套了。为此需要将旧的类型转换成新的、函数原型做对应调整。庆幸的是，C实现的
&lt;code&gt;matrix&lt;/code&gt; 模块并不需要传入参数，逻辑不用作过多修改。&lt;/p&gt;
&lt;p&gt;不过也可以看出这个 matrix 模块不够灵活，是针对 M60 键盘写死的，想迁移到别的设备还得动点脑筋。&lt;/p&gt;
&lt;h3 id="安全模式没了"&gt;安全模式没了&lt;a class="headerlink" href="#安全模式没了" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;为什么我无法手动进入安全模式？安全模式下，不会自动重载代码，不会上电自动启动代
码，方便调试板上的 python 程序，同时也可以作为一种更新板上代码的方式。为了避免
键盘的虚拟U盘文件系统损坏，我会在 &lt;code&gt;boot.py&lt;/code&gt; 中关闭存储设备接口或将存储设置为只
读。有时代码没写好，忘了加恢复的快捷键，就需要用安全模式避免代码上电执行。但现
在安全模式没了，就又得格式化存储区……&lt;/p&gt;
&lt;p&gt;检查了 cherry-pick 的 commit，发现问题可能出在绕过 &lt;code&gt;soft device&lt;/code&gt; 崩溃时的安全模
式重启上。reset 键盘时， &lt;code&gt;soft device&lt;/code&gt; 可能会挂掉，而现在的状况是，手动进入安全
模式， &lt;code&gt;soft device&lt;/code&gt; 也会挂掉，所以原来的代码顺带把进入安全模式的状态给清了……&lt;/p&gt;
&lt;p&gt;解决方法，便是对当前重置条件做额外检查。运气很好，其它模块内有对应实现，搬过来稍微改改就好了。&lt;/p&gt;
&lt;h3 id="用-mpy-cross-提前将模块编译成字节码"&gt;用 mpy-cross 提前将模块编译成字节码&lt;a class="headerlink" href="#用-mpy-cross-提前将模块编译成字节码" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;需要用与 CircuitPython 版本对应的 mpy-cross&lt;sup id="fnref:mpy-cross-micropython"&gt;&lt;a class="footnote-ref" href="#fn:mpy-cross-micropython"&gt;5&lt;/a&gt;&lt;/sup&gt;，参数设定好。本来想继续
研究看看，但好像没什么用。&lt;/p&gt;
&lt;p&gt;还想研究 C 写的模块来着，发现 CircuitPython 还不行……这边 MicroPython 更先进啊。&lt;/p&gt;
&lt;h2 id="修-bug"&gt;修 Bug&lt;a class="headerlink" href="#修-bug" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="让官方版本-circuitpython-支持无-usb-连接使用"&gt;让官方版本 CircuitPython 支持无 USB 连接使用&lt;a class="headerlink" href="#让官方版本-circuitpython-支持无-usb-连接使用" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;其实很简单，回过头来看，只要把键盘的电源控制IO（P0.28）激活即可。Makerdiary在添加支持时并没有通过board暴露这个引脚，文档里也有点错误。总之搞定了。CPY 8.2.0+应该都能用了。&lt;/p&gt;
&lt;p&gt;===&lt;/p&gt;
&lt;p&gt;先这样写一点，之后应该不会再补充了。&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:circuitpython"&gt;
&lt;p&gt;&lt;a href="https://circuitpython.org"&gt;CircuitPython 主页&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:circuitpython" title="回到 #1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:circuitpython-git"&gt;
&lt;p&gt;&lt;a href="https://github.com/adafruit/circuitpython"&gt;CircuitPython 仓库&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:circuitpython-git" title="回到 #2"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:pykb-makerdiary"&gt;
&lt;p&gt;&lt;a href="https://github.com/makerdiary/python-keyboard"&gt;Makerdiary 的 M60 键盘&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:pykb-makerdiary" title="回到 #3"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:compare-makerdiary-adafruit"&gt;
&lt;p&gt;&lt;a href="https://github.com/adafruit/circuitpython/compare/main...xiongyihui:circuitpython:m60"&gt;比较 Makerdiary 和 和 Adafruit 官方版两个分支的不同&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:compare-makerdiary-adafruit" title="回到 #4"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:mpy-cross-micropython"&gt;
&lt;p&gt;&lt;a href="https://docs.micropython.org/en/latest/reference/mpyfiles.html"&gt;MicroPython 对 mpy、mpy-cross 的介绍&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:mpy-cross-micropython" title="回到 #5"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:pykb-hyx0329"&gt;
&lt;p&gt;&lt;a href="https://github.com/hyx0329/python-keyboard"&gt;M60 键盘 - hyx0329 定制版固件&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:pykb-hyx0329" title="回到 #6"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:mpy-c-module"&gt;
&lt;p&gt;&lt;a href="https://docs.micropython.org/en/latest/develop/natmod.html"&gt;MicroPython 用 C 编写 native 模块&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:mpy-c-module" title="回到 #7"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="Embedded"/><category term="circuitpython"/><category term="nrf52"/></entry><entry><title>重新认识 Vim</title><link href="https://techie-s.work/posts/2022/06/vim-refresh/" rel="alternate"/><published>2022-06-04T00:00:00+08:00</published><updated>2022-09-11T00:00:00+08:00</updated><author><name/></author><id>tag:techie-s.work,2022-06-04:/posts/2022/06/vim-refresh/</id><summary type="html">&lt;p&gt;自我接触 GNU/Linux 已有 7 年，用了那么多次 Vim，却还停留在这个水平……感觉不太行，而且确实少了
点生产力。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;需要重新认识一下 Vim …&lt;/strong&gt;&lt;/p&gt;</summary><content type="html">
&lt;p&gt;自我接触 GNU/Linux 已有 7 年，用了那么多次 Vim，却还停留在这个水平……感觉不太行，而且确实少了
点生产力。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;需要重新认识一下 Vim 了！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;（本文不定期更新）&lt;/p&gt;
&lt;p&gt;一个我认为不错的 Vim primer：&lt;a href="https://danielmiessler.com/study/vim/"&gt;Learn Vim For the Last Time: A Tutorial and Primer&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="有关-vim-的一些问题"&gt;有关 Vim 的一些问题&lt;a class="headerlink" href="#有关-vim-的一些问题" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="vim-是什么"&gt;Vim 是什么？&lt;a class="headerlink" href="#vim-是什么" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;hmm，简单说就是一个 TUI（Text-based User Interface）环境下的文本编辑器。
&lt;s&gt;和 Emacs 是某种意义上的竞争对手。&lt;/s&gt;&lt;/p&gt;
&lt;h3 id="为什么用-vim它有什么优势吗"&gt;为什么用 Vim？它有什么优势吗？&lt;a class="headerlink" href="#为什么用-vim它有什么优势吗" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;我选用 Vim 没什么特别的理由。它的界面极其简洁，插件功能非常丰富，适合终端下使用。其它的嘛，其实
也不能就这样和别的编辑器比。&lt;/p&gt;
&lt;p&gt;相比于现代编辑器，Vim 更依赖键盘快捷键，学习曲线较陡，而且在 GUI 环境下并不一定比其它编辑器更强。
不过我现在就基本在命令行环境下开发，学点 Vim 貌似更合适，而且命令行下、围绕键盘的操作方式更适合
熟练人员、笔记本电脑用户。&lt;/p&gt;
&lt;p&gt;当然这也只是我自己的感受。&lt;/p&gt;
&lt;h3 id="学习的思路为何"&gt;学习的思路为何？&lt;a class="headerlink" href="#学习的思路为何" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;首先，接受 Vim 的设计特色。Vim、Emacs 等软件并非像常用 GUI 编辑器那样考虑到了很多鼠标操作，而
在认识到他们围绕键盘操作的特色后，也能理解为什么需要优先学习快捷键了。&lt;/p&gt;
&lt;p&gt;其次，学习 Vim 中等价于其它编辑器的功能。这部分优先学会，才能让使用者实现用 Vim 代替原有的编辑
器，将整个环境替换过去。&lt;/p&gt;
&lt;p&gt;之后就是喜闻乐见的进阶技巧了。宏、模块/插件之类是非常高效的功能。&lt;/p&gt;
&lt;h2 id="我的-vim-使用记录"&gt;我的 Vim 使用记录&lt;a class="headerlink" href="#我的-vim-使用记录" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="2022-09-11"&gt;2022-09-11&lt;a class="headerlink" href="#2022-09-11" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;感觉这样记录不太行……中间都直接不记录了……&lt;/p&gt;
&lt;h3 id="2022-07-29"&gt;2022-07-29&lt;a class="headerlink" href="#2022-07-29" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;到这个时候，我虽然还不能说熟练使用 Vim，但是一些基本功能也已经掌握了，而且通过插件的帮助改善了编
程体验。前面好多学习经历都没有记录。&lt;/p&gt;
&lt;p&gt;目前学会的部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基本的文件浏览&lt;ul&gt;
&lt;li&gt;上下左右滚屏&lt;/li&gt;
&lt;li&gt;跳转&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;基本的编辑&lt;ul&gt;
&lt;li&gt;verbs，noun，adverbs&lt;/li&gt;
&lt;li&gt;重复&lt;/li&gt;
&lt;li&gt;查找&lt;/li&gt;
&lt;li&gt;替换&lt;/li&gt;
&lt;li&gt;快速改写/删除&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content><category term="Software"/><category term="vim"/><category term="refresh"/></entry><entry><title>MicroPython 上手</title><link href="https://techie-s.work/posts/2022/05/micropython-001-hands-on/" rel="alternate"/><published>2022-05-11T00:00:00+08:00</published><updated>2022-05-11T00:00:00+08:00</updated><author><name/></author><id>tag:techie-s.work,2022-05-11:/posts/2022/05/micropython-001-hands-on/</id><summary type="html">&lt;p&gt;虽说是上手，但实际我有玩过一阵子了，而且这篇文更接近介绍。本系列博客(?)将尝试给有基础单片机概念的人快速了解 MicroPython …&lt;/p&gt;</summary><content type="html">
&lt;p&gt;虽说是上手，但实际我有玩过一阵子了，而且这篇文更接近介绍。本系列博客(?)将尝试给有基础单片机概念的人快速了解 MicroPython。&lt;/p&gt;
&lt;h2 id="介绍"&gt;介绍&lt;a class="headerlink" href="#介绍" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/micropython/micropython"&gt;MicroPython&lt;/a&gt; 是一款面向嵌入式设备的 Python 环境，运行于各种单片机上，
Adafruit fork 出的 &lt;a href="https://github.com/adafruit/circuitpython"&gt;CircuitPython&lt;/a&gt; 是一个对新手更友好的分支，并且是CPython的严格子集，不过也缺少了一部分高级特性，这个参考官方仓库说明。&lt;/p&gt;
&lt;p&gt;MicroPython 主要通过串口实现 REPL（Read Eval Print Loop）的交互，也就是 Python Console。
此外在支持的设备上还能使用网络，从网页或者 Telnet 客户端进行操作。&lt;/p&gt;
&lt;p&gt;MicroPython 的语法接近 CPython，许多常用标准库能代换，但相比之下精简了很多，并且添加了一部分独有的库，这些独有库通常以字母 &lt;code&gt;u&lt;/code&gt; 开头。&lt;/p&gt;
&lt;h2 id="使用前言"&gt;使用前言&lt;a class="headerlink" href="#使用前言" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;想使用 MicroPython？买一块兼容开发板，刷上固件，然后就能 Happy 啦！
主要支持的设备是部分基于 STM32、NRF52、ESP32 的开发板。具体支持哪些板子可以看&lt;a href="https://micropython.org/download/"&gt;官方下载页面&lt;/a&gt;。我个人比较推荐纯新手且仅打算使用 Py 编程的朋友选用基于 ESP32 系列 MCU 的开发板，比如 Node32、Lolin32、LuatOS Core。&lt;/p&gt;
&lt;p&gt;我选择的是来自 LuatOS 团队的开发板，它使用 ESP32-C3 这款 RISCV 架构的芯片，支持蓝牙、802.11 b/g/n/e/i，我选购的时候它价格也不错。就是不在官方支持列表上XD。&lt;/p&gt;
&lt;p&gt;官方的下载渠道在&lt;a href="https://micropython.org/download/"&gt;这里&lt;/a&gt;，根据不同的开发板，点进去后会有不同的安装说明。&lt;/p&gt;
&lt;h2 id="给-luatos-core-安装-micropython"&gt;给 LuatOS Core 安装 MicroPython&lt;a class="headerlink" href="#给-luatos-core-安装-micropython" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这里就写一下这款开发板的安装流程吧。ESP32 系列的其实都差不多，而且一般不需要调试器。&lt;/p&gt;
&lt;p&gt;大概分这么几步：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;下载工具&lt;/li&gt;
&lt;li&gt;下载固件&lt;/li&gt;
&lt;li&gt;清空 Flash&lt;/li&gt;
&lt;li&gt;写入固件&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="下载工具"&gt;下载工具&lt;a class="headerlink" href="#下载工具" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这里需要安装 &lt;code&gt;esptool.py&lt;/code&gt; 或者乐鑫提供的 Windows 版刷写工具。工具之类我就不再介绍如何安装了，希望读者能自行搞定（尽管这个可能是大多数人最开始搞不定的地方）。&lt;/p&gt;
&lt;h3 id="下载固件-清空-flash-写入固件"&gt;下载固件 &amp;amp; 清空 Flash &amp;amp; 写入固件&lt;a class="headerlink" href="#下载固件-清空-flash-写入固件" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;由于我这块板子没有进入官方支持列表，有两种选择：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用官方通用固件&lt;/li&gt;
&lt;li&gt;自行补充开发板定义，并编译&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我实际上选择了第二种，从结果上看第一种也没差。这里按第一种方法。ESP32-C3 的官方固件下载和说明页面&lt;a href="https://micropython.org/download/esp32c3/"&gt;点击这里&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;Windows 用户请自行解决工具问题。&lt;/p&gt;
&lt;p&gt;对于 Linux 用户，打开命令行终端，使用下面的命令进行刷写。注意，清空 Flash 是为了避免残留数据干扰固件第一次建立文件系统，在第一次刷写时不可避免（当然空 Flash 除外）。&lt;/p&gt;
&lt;p&gt;另外，我用的开发板只用了两条数据线读写 Flash，务必选用 DIO 模式。 &lt;code&gt;/dev/ttyACM0&lt;/code&gt; 是串口的设备路径，请根据实际情况调整。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 清空 Flash&lt;/span&gt;
esptool.py&lt;span class="w"&gt; &lt;/span&gt;--chip&lt;span class="w"&gt; &lt;/span&gt;esp32c3&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;/dev/ttyACM0&lt;span class="w"&gt; &lt;/span&gt;erase_flash
&lt;span class="c1"&gt;# 刷写固件，强制 DIO 模式，波特率460800，刷写固件，从0x0地址开始写入，固件名称 esp32c3-20220117-v1.18.bin&lt;/span&gt;
esptool.py&lt;span class="w"&gt; &lt;/span&gt;--chip&lt;span class="w"&gt; &lt;/span&gt;esp32c3&lt;span class="w"&gt; &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;/dev/ttyACM0&lt;span class="w"&gt; &lt;/span&gt;--flash_mode&lt;span class="w"&gt; &lt;/span&gt;dio&lt;span class="w"&gt; &lt;/span&gt;--baud&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;460800&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;write_flash&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;0x0&lt;span class="w"&gt; &lt;/span&gt;esp32c3-20220117-v1.18.bin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这样固件就写入完成了。下篇开始正式玩XD。&lt;/p&gt;
&lt;h2 id="结语"&gt;结语&lt;a class="headerlink" href="#结语" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这样应该就算上手完了？不是吧 XP。嗯，实际上应该再写一点第一次使用时使用的 Demo，比如 Hello World、Blinky 什么的。
但对于希望小白能全看懂的我来说，或许更适合放在一篇单独的文章中。但实际写出来的好像也不是非常容易看懂啊……&lt;/p&gt;</content><category term="MicroPython"/><category term="embedded"/><category term="python"/><category term="esp32c3"/><category term="tutorial"/></entry></feed>