<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Good good study, day day up</title>
  
  <subtitle>aleung的学习笔记, aleung的idea</subtitle>
  <link href="http://aleung.github.io/blog/atom.xml" rel="self"/>
  
  <link href="http://aleung.github.io/blog/"/>
  <updated>2024-01-18T15:41:12.086Z</updated>
  <id>http://aleung.github.io/blog/</id>
  
  <author>
    <name>Leo Liang</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>烤箱机械温控</title>
    <link href="http://aleung.github.io/blog/2024/01/18/oven-temperature-control/"/>
    <id>http://aleung.github.io/blog/2024/01/18/oven-temperature-control/</id>
    <published>2024-01-18T15:30:42.000Z</published>
    <updated>2024-01-18T15:41:12.086Z</updated>
    
    <content type="html"><![CDATA[<p>拆开烤箱看过才知道机械温控器的双金属片就是安装在温控旋钮后面，因此它测量的是温控旋钮处的温度，而不是烤箱内腔里的温度。</p><p><img src="/blog/2024/01/18/oven-temperature-control/oven.jpg"></p><p>找 Bard 问一下，回答是这样的：</p><blockquote><p>由于机械温控器的温度传感器距离烤箱内部较远，因此测量结果会受到外界环境的影响，例如烤箱门的密封程度、烤箱内腔材质等。因此，机械温控器的温控精度通常较低，在烘焙等对温度要求较高的场合，不建议使用机械温控器的烤箱。</p><p>电子温控器的温度传感器通常安装在烤箱内部，因此测量结果更准确。电子温控器还可以通过微电路进行温度控制，可以实现更精准的恒温效果。因此，在烘焙等对温度要求较高的场合，建议使用电子温控器的烤箱。</p></blockquote><p>虽然知道机械的不会太准，没想到也太离谱了，这样受环境的影响大，腔内温度变化的反馈也很慢。看来还是得电子温控的才靠谱，要不就必须要在烤箱内放温度计，人工 PID 😀</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;拆开烤箱看过才知道机械温控器的双金属片就是安装在温控旋钮后面，因此它测量的是温控旋钮处的温度，而不是烤箱内腔里的温度。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/2024/01/18/oven-temperature-control/oven.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;找 Bard 问一下，回答是这样的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;由于机械温控器的温度传感器距离烤箱内部较远，因此测量结果会受到外界环境的影响，例如烤箱门的密封程度、烤箱内腔材质等。因此，机械温控器的温控精度通常较低，在烘焙等对温度要求较高的场合，不建议使用机械温控器的烤箱。&lt;/p&gt;
&lt;p&gt;电子温控器的温度传感器通常安装在烤箱内部，因此测量结果更准确。电子温控器还可以通过微电路进行温度控制，可以实现更精准的恒温效果。因此，在烘焙等对温度要求较高的场合，建议使用电子温控器的烤箱。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;虽然知道机械的不会太准，没想到也太离谱了，这样受环境的影响大，腔内温度变化的反馈也很慢。看来还是得电子温控的才靠谱，要不就必须要在烤箱内放温度计，人工 PID 😀&lt;/p&gt;</summary>
    
    
    
    
    <category term="HomeAppliance" scheme="http://aleung.github.io/blog/tags/HomeAppliance/"/>
    
  </entry>
  
  <entry>
    <title>NAS and file system</title>
    <link href="http://aleung.github.io/blog/2022/12/29/NAS-and-file-system/"/>
    <id>http://aleung.github.io/blog/2022/12/29/NAS-and-file-system/</id>
    <published>2022-12-29T14:44:15.000Z</published>
    <updated>2024-01-18T15:28:27.018Z</updated>
    
    <content type="html"><![CDATA[<p>折腾了Linux台式机和NAS，记录一下。(原文写于2022年底，但放下了就没有全部写完)</p><p>其实我没有很高的NAS的服务需求，主要是数据得有备份，因此也没有专门买NAS硬件也没有用专门的NAS软件，只是用了闲置下来一台台式机，安装Ubuntu。故此，我就有两台Ubuntu电脑，一台是日常用机，称为desktop，另一台是NAS。</p><h2 id="NAS"><a href="#NAS" class="headerlink" title="NAS"></a>NAS</h2><h3 id="Btrfs-RAID-1"><a href="#Btrfs-RAID-1" class="headerlink" title="Btrfs RAID 1"></a>Btrfs RAID 1</h3><p>NAS需要保障数据安全，组个RAID会安心一点。</p><p>这台电脑用了一个128G的SSD作为系统盘，挂两个HDD组成RAID 1做数据盘，以后有需求可以再增加一个。</p><p>数据盘的文件系统采用BTRFS，考虑BTRFS支持RAID，并且可以用多个容量不一的磁盘来组RAID 1，正好用上现有的硬盘，以后添加也可以根据到时最经济的大小来买。添加（<code>btrfs device add</code>）或者替换（<code>btrfs replace</code>）磁盘后，执行 <code>btrfs balance</code> 就会将数据在RAID中的磁盘中重新分配，它会根据数据副本数量自动考虑各个磁盘的空闲空间大小来分配。</p><p>BTRFS 的 scrub 命令可以检查和修复一部分磁盘错误。它对每个数据块读取并检查 checksum，如果发现读取失败或者数据校验错误，就会尝试用 RAID 1 中的其他副本来替换。建议定期（例如每个月）执行。</p><h2 id="Desktop"><a href="#Desktop" class="headerlink" title="Desktop"></a>Desktop</h2><p>工作电脑要解决的问题是磁盘容量和性能的平衡，还有文件的历史版本保存，方便出问题时回退。</p><p>首先，系统根文件系统我不想搞复杂，怕出问题，还是简单在SSD上分配，用 ext4 文件系统。然后，home 和一些数据目录再考虑其他方案。</p><h3 id="Cached-LVM"><a href="#Cached-LVM" class="headerlink" title="Cached LVM"></a>Cached LVM</h3><p>照片、视频、数据等等各种东西加起来，需要的磁盘容量不小。考虑到使用方便和存储速度，倾向于保存在desktop上，而不是从NAS远程访问。</p><p>如果都用SSD，大容量SSD也太贵了，HDD速度又太慢。以前用黑苹果的时候，是用 SSD + HDD 组成了 fusion drive，体验相当好，故此在 Linux 下也寻找类似的方案。最终选择了使用 LVM cache。</p><p>LVM 的基本概念是三层结构：</p><ul><li>LV - Logical Volume，在VG上分配LV，LV上创建文件系统</li><li>VG - Volume Group，多个PV组合成一个VG</li><li>PV - Physical Volume，对应着物理存储设备（磁盘或者分区）</li></ul><p>一个 VG 中的 PV 可以包括 SSD（高速）和 HDD（低速），cached LV 就是将一个 cache LV 和一个 main LV 绑定起来，其中 cache LV 的空间分配自高速的 PV，而 main LV 的空间分配自低速的 PV，利用 cache 对读写操作进行缓冲，提高访问速度。</p><p>Cached LV 有两种类型：cache both read and write (type  <code>cache</code>) 和 cache only write operations (type <code>writecache</code> )，在我的应用场景中当然是希望要能够对读操作加速。有两种 cache 模式：writeback 和 writethrough，writethrough 安全但似乎就起不到加速写操作的作用，但如果指定 writeback 模式，创建的时候会有警告，不推荐使用。</p><p>很多教程、文档上写的方法都是一步步按部就班进行：</p><ol><li>创建 main LV</li><li>创建 cache data LV</li><li>创建 cache meta LV</li><li>将 cache data LV 和 cache meta LV 合并为一个 cache pool</li><li>将 cache pool 绑定到 main LV，转换为 cached LV</li></ol><p>其实用 cachevol 比用 cache pool 要简单， 就是直接在高速 PV 上创建一个 LV，将这个 LV 作为 cache。它与 cache pool 不同在于 cachevol 无法将 data 与 meta 分开到不同磁盘上，但通常家用场景也没有这样的需求</p><p>然而，还有一个终极大招：一个命令完成创建 main LV （例子中为<code>/dev/sdd2</code>）、创建 cachevol (例子中为<code>/dev/sda5</code>)、转换为 cached LV 的全过程：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lvcreate --type cache -l 100%FREE --cachedevice /dev/sda5 vg2 -n lv_name /dev/sdd2</span><br></pre></td></tr></table></figure><p>如果在创建的过程中出现关于 chunksize 的错误信息，需要增加参数 <code>--chunksize</code> 来调整。超过100GB的cache需要256k chunk size。</p><p>这样组成的 cached LV 跟 MacOS 的 APFS Fusoin Drive 还是有区别：cached LV 的有效大小是 main LV 的大小，cache volume 完全只是作为 cache，甚至可以把它摘掉也不会影响存储的数据；而 Fusion Drive 的容量是组成的多个分区的总大小，它内部自动将文件在不同设备之间迁移，尽可能将热数据保存在快速设备上。</p><h3 id="Btrfs-subvolumes-and-snapshots"><a href="#Btrfs-subvolumes-and-snapshots" class="headerlink" title="Btrfs, subvolumes and snapshots"></a>Btrfs, subvolumes and snapshots</h3><p>上面步骤创建好的 LV 等价于磁盘分区，还需要在上面创建文件系统。考虑到 BTRFS 支持 snapshot，可以实现文件的历史版本记录，选择了使用BTRFS。</p><h4 id="subvolume"><a href="#subvolume" class="headerlink" title="subvolume"></a>subvolume</h4><p>(没写完)</p><h4 id="snapper"><a href="#snapper" class="headerlink" title="snapper"></a>snapper</h4><p>snapper是一个管理 btrfs snapshot 的工具，对每个 subvolume 配置 snapshot 的保留策略（每小时&#x2F;每天&#x2F;每周&#x2F;每月&#x2F;每年的分别保存多少份），定时创建&#x2F;清理 btrfs snapshot。</p><p>snapper是命令行工具，有一个snapper-gui项目，但在Ubuntu上工作不是很正常。用<a href="https://gitlab.com/btrfs-assistant/btrfs-assistant">btrfs-assistant</a> GUI tool, 代替了 snapper-gui</p><p>实际使用中发现，如果文件系统剩余空间不多（例如不足10%），磁盘就会非常繁忙，系统卡顿，应该是数据整理非常耗费资源。要保证文件系统剩余足够空闲空间，这限制的snapshot的数量。另外一个问题是，删除snapshot的时候，磁盘会非常繁忙，因此也要减少 snapshot 的数量。期望单靠 snapshot 来找回文件历史版本并不是合适的方案，应该还是使用 backup 工具创建定期的备份（见下面的章节），而对于需要<strong>真正</strong>的文件版本的场合，使用专门的版本管理系统。</p><p>如果重新再装系统，我就会考虑snapshot这个功能需求是否有那么重要了，也许并不需要使用btrfs。</p><h4 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h4><p>启用 quota 功能可以统计各个 subvolume 使用的空间大小：<code>btrfs enable quota /path</code>，但是 quota 会造成很严重的性能问题！所以没必要还是不要启用。</p><p><a href="https://github.com/speed47/btrfs-list">btrfs-list</a> 方便查看所有的 subvolume, snapshot 以及它们所占的空间大小</p><h2 id="Backup"><a href="#Backup" class="headerlink" title="Backup"></a>Backup</h2><p>从desktop备份数据到NAS，用的是 <a href="https://kopia.io/">Kopia</a>。类似的工具还有 <a href="https://www.borgbackup.org/">Borg</a>，都是deduplication的备份工具，对于多个备份中的相同的数据（基于hash）不会重复传输和存储，可以高效的进行增量备份，节省存储空间。</p><p>Kopia既有命令行也有GUI，我在desktop上安装了KopiaUI，使用相当方便。存储仓库用SFTP指向NAS，在NAS上不需要安装软件，配置好ssh就可以。</p><p>在desktop上，为每个要备份的目录（我这里是对应着BTRFS的subvolume）创建一个policy，policy中可以配置备份保持的周期（每小时&#x2F;每天&#x2F;每周&#x2F;每月&#x2F;每年的分别保存多少份），执行间隔时间，设定是否压缩，等等。</p><p>如果有些文件&#x2F;目录不想备份，可以写到<code>.kopiaignore</code>文件中，类似于<code>.gitignore</code>的格式，这也很方便，比起在软件中配置要简单。</p><p>Desktop 上使用 BTRFS，并且已经使用 snapper 定时（每小时）创建 Btrfs snapshot，那最好是能够让备份工具备份最近一个文件系统snapshot，而不是直接备份当前文件，这样的好处是能够保证一个备份中的文件都是处于同一个时刻的，而不是有时间先后。</p><p>Kopia 的policy支持配置 before-snapshot action，正好可以做这个事情。这个 action 的脚本做的事情就是找过要备份的 btrfs subvolume 中最后一个 snapshot 的路径：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">snapshots=<span class="variable">$KOPIA_SOURCE_PATH</span>/.snapshots</span><br><span class="line">latest=`<span class="built_in">ls</span> -Art <span class="variable">$snapshots</span> | <span class="built_in">tail</span> -n 1`</span><br><span class="line"><span class="built_in">echo</span> KOPIA_SNAPSHOT_PATH=<span class="variable">$snapshots</span>/<span class="variable">$latest</span>/snapshot</span><br></pre></td></tr></table></figure><p>Kopia 会备份这个 action 返回的 KOPIA_SNAPSHOT_PATH，而不是原始的 KOPIA_SOURCE_PATH。</p><p>以前用MacOS，它的 TimeMachine 很好用的一点是可以在 timeline 中很方便的浏览备份的文件，快速的定位到想要恢复的文件在那个日期的备份中存在。看了一轮 Linux 上的备份工具的介绍，在数据恢复方面都没有能够像 TimeMachine 那么方便的，需要一个个备份去查看。有些工具能够提供文件在不同备份之间 diff 的功能，可惜 Kopia 也没有。</p><span id="more"></span>]]></content>
    
    
    <summary type="html">&lt;p&gt;折腾了Linux台式机和NAS，记录一下。(原文写于2022年底，但放下了就没有全部写完)&lt;/p&gt;
&lt;p&gt;其实我没有很高的NAS的服务需求，主要是数据得有备份，因此也没有专门买NAS硬件也没有用专门的NAS软件，只是用了闲置下来一台台式机，安装Ubuntu。故此，我就有两台Ubuntu电脑，一台是日常用机，称为desktop，另一台是NAS。&lt;/p&gt;
&lt;h2 id=&quot;NAS&quot;&gt;&lt;a href=&quot;#NAS&quot; class=&quot;headerlink&quot; title=&quot;NAS&quot;&gt;&lt;/a&gt;NAS&lt;/h2&gt;&lt;h3 id=&quot;Btrfs-RAID-1&quot;&gt;&lt;a href=&quot;#Btrfs-RAID-1&quot; class=&quot;headerlink&quot; title=&quot;Btrfs RAID 1&quot;&gt;&lt;/a&gt;Btrfs RAID 1&lt;/h3&gt;&lt;p&gt;NAS需要保障数据安全，组个RAID会安心一点。&lt;/p&gt;
&lt;p&gt;这台电脑用了一个128G的SSD作为系统盘，挂两个HDD组成RAID 1做数据盘，以后有需求可以再增加一个。&lt;/p&gt;
&lt;p&gt;数据盘的文件系统采用BTRFS，考虑BTRFS支持RAID，并且可以用多个容量不一的磁盘来组RAID 1，正好用上现有的硬盘，以后添加也可以根据到时最经济的大小来买。添加（&lt;code&gt;btrfs device add&lt;/code&gt;）或者替换（&lt;code&gt;btrfs replace&lt;/code&gt;）磁盘后，执行 &lt;code&gt;btrfs balance&lt;/code&gt; 就会将数据在RAID中的磁盘中重新分配，它会根据数据副本数量自动考虑各个磁盘的空闲空间大小来分配。&lt;/p&gt;
&lt;p&gt;BTRFS 的 scrub 命令可以检查和修复一部分磁盘错误。它对每个数据块读取并检查 checksum，如果发现读取失败或者数据校验错误，就会尝试用 RAID 1 中的其他副本来替换。建议定期（例如每个月）执行。&lt;/p&gt;
&lt;h2 id=&quot;Desktop&quot;&gt;&lt;a href=&quot;#Desktop&quot; class=&quot;headerlink&quot; title=&quot;Desktop&quot;&gt;&lt;/a&gt;Desktop&lt;/h2&gt;&lt;p&gt;工作电脑要解决的问题是磁盘容量和性能的平衡，还有文件的历史版本保存，方便出问题时回退。&lt;/p&gt;
&lt;p&gt;首先，系统根文件系统我不想搞复杂，怕出问题，还是简单在SSD上分配，用 ext4 文件系统。然后，home 和一些数据目录再考虑其他方案。&lt;/p&gt;
&lt;h3 id=&quot;Cached-LVM&quot;&gt;&lt;a href=&quot;#Cached-LVM&quot; class=&quot;headerlink&quot; title=&quot;Cached LVM&quot;&gt;&lt;/a&gt;Cached LVM&lt;/h3&gt;&lt;p&gt;照片、视频、数据等等各种东西加起来，需要的磁盘容量不小。考虑到使用方便和存储速度，倾向于保存在desktop上，而不是从NAS远程访问。&lt;/p&gt;
&lt;p&gt;如果都用SSD，大容量SSD也太贵了，HDD速度又太慢。以前用黑苹果的时候，是用 SSD + HDD 组成了 fusion drive，体验相当好，故此在 Linux 下也寻找类似的方案。最终选择了使用 LVM cache。&lt;/p&gt;
&lt;p&gt;LVM 的基本概念是三层结构：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LV - Logical Volume，在VG上分配LV，LV上创建文件系统&lt;/li&gt;
&lt;li&gt;VG - Volume Group，多个PV组合成一个VG&lt;/li&gt;
&lt;li&gt;PV - Physical Volume，对应着物理存储设备（磁盘或者分区）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个 VG 中的 PV 可以包括 SSD（高速）和 HDD（低速），cached LV 就是将一个 cache LV 和一个 main LV 绑定起来，其中 cache LV 的空间分配自高速的 PV，而 main LV 的空间分配自低速的 PV，利用 cache 对读写操作进行缓冲，提高访问速度。&lt;/p&gt;
&lt;p&gt;Cached LV 有两种类型：cache both read and write (type  &lt;code&gt;cache&lt;/code&gt;) 和 cache only write operations (type &lt;code&gt;writecache&lt;/code&gt; )，在我的应用场景中当然是希望要能够对读操作加速。有两种 cache 模式：writeback 和 writethrough，writethrough 安全但似乎就起不到加速写操作的作用，但如果指定 writeback 模式，创建的时候会有警告，不推荐使用。&lt;/p&gt;
&lt;p&gt;很多教程、文档上写的方法都是一步步按部就班进行：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建 main LV&lt;/li&gt;
&lt;li&gt;创建 cache data LV&lt;/li&gt;
&lt;li&gt;创建 cache meta LV&lt;/li&gt;
&lt;li&gt;将 cache data LV 和 cache meta LV 合并为一个 cache pool&lt;/li&gt;
&lt;li&gt;将 cache pool 绑定到 main LV，转换为 cached LV&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其实用 cachevol 比用 cache pool 要简单， 就是直接在高速 PV 上创建一个 LV，将这个 LV 作为 cache。它与 cache pool 不同在于 cachevol 无法将 data 与 meta 分开到不同磁盘上，但通常家用场景也没有这样的需求&lt;/p&gt;
&lt;p&gt;然而，还有一个终极大招：一个命令完成创建 main LV （例子中为&lt;code&gt;/dev/sdd2&lt;/code&gt;）、创建 cachevol (例子中为&lt;code&gt;/dev/sda5&lt;/code&gt;)、转换为 cached LV 的全过程：&lt;/p&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;lvcreate --type cache -l 100%FREE --cachedevice /dev/sda5 vg2 -n lv_name /dev/sdd2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;如果在创建的过程中出现关于 chunksize 的错误信息，需要增加参数 &lt;code&gt;--chunksize&lt;/code&gt; 来调整。超过100GB的cache需要256k chunk size。&lt;/p&gt;
&lt;p&gt;这样组成的 cached LV 跟 MacOS 的 APFS Fusoin Drive 还是有区别：cached LV 的有效大小是 main LV 的大小，cache volume 完全只是作为 cache，甚至可以把它摘掉也不会影响存储的数据；而 Fusion Drive 的容量是组成的多个分区的总大小，它内部自动将文件在不同设备之间迁移，尽可能将热数据保存在快速设备上。&lt;/p&gt;
&lt;h3 id=&quot;Btrfs-subvolumes-and-snapshots&quot;&gt;&lt;a href=&quot;#Btrfs-subvolumes-and-snapshots&quot; class=&quot;headerlink&quot; title=&quot;Btrfs, subvolumes and snapshots&quot;&gt;&lt;/a&gt;Btrfs, subvolumes and snapshots&lt;/h3&gt;&lt;p&gt;上面步骤创建好的 LV 等价于磁盘分区，还需要在上面创建文件系统。考虑到 BTRFS 支持 snapshot，可以实现文件的历史版本记录，选择了使用BTRFS。&lt;/p&gt;
&lt;h4 id=&quot;subvolume&quot;&gt;&lt;a href=&quot;#subvolume&quot; class=&quot;headerlink&quot; title=&quot;subvolume&quot;&gt;&lt;/a&gt;subvolume&lt;/h4&gt;&lt;p&gt;(没写完)&lt;/p&gt;
&lt;h4 id=&quot;snapper&quot;&gt;&lt;a href=&quot;#snapper&quot; class=&quot;headerlink&quot; title=&quot;snapper&quot;&gt;&lt;/a&gt;snapper&lt;/h4&gt;&lt;p&gt;snapper是一个管理 btrfs snapshot 的工具，对每个 subvolume 配置 snapshot 的保留策略（每小时&amp;#x2F;每天&amp;#x2F;每周&amp;#x2F;每月&amp;#x2F;每年的分别保存多少份），定时创建&amp;#x2F;清理 btrfs snapshot。&lt;/p&gt;
&lt;p&gt;snapper是命令行工具，有一个snapper-gui项目，但在Ubuntu上工作不是很正常。用&lt;a href=&quot;https://gitlab.com/btrfs-assistant/btrfs-assistant&quot;&gt;btrfs-assistant&lt;/a&gt; GUI tool, 代替了 snapper-gui&lt;/p&gt;
&lt;p&gt;实际使用中发现，如果文件系统剩余空间不多（例如不足10%），磁盘就会非常繁忙，系统卡顿，应该是数据整理非常耗费资源。要保证文件系统剩余足够空闲空间，这限制的snapshot的数量。另外一个问题是，删除snapshot的时候，磁盘会非常繁忙，因此也要减少 snapshot 的数量。期望单靠 snapshot 来找回文件历史版本并不是合适的方案，应该还是使用 backup 工具创建定期的备份（见下面的章节），而对于需要&lt;strong&gt;真正&lt;/strong&gt;的文件版本的场合，使用专门的版本管理系统。&lt;/p&gt;
&lt;p&gt;如果重新再装系统，我就会考虑snapshot这个功能需求是否有那么重要了，也许并不需要使用btrfs。&lt;/p&gt;
&lt;h4 id=&quot;其他&quot;&gt;&lt;a href=&quot;#其他&quot; class=&quot;headerlink&quot; title=&quot;其他&quot;&gt;&lt;/a&gt;其他&lt;/h4&gt;&lt;p&gt;启用 quota 功能可以统计各个 subvolume 使用的空间大小：&lt;code&gt;btrfs enable quota /path&lt;/code&gt;，但是 quota 会造成很严重的性能问题！所以没必要还是不要启用。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/speed47/btrfs-list&quot;&gt;btrfs-list&lt;/a&gt; 方便查看所有的 subvolume, snapshot 以及它们所占的空间大小&lt;/p&gt;
&lt;h2 id=&quot;Backup&quot;&gt;&lt;a href=&quot;#Backup&quot; class=&quot;headerlink&quot; title=&quot;Backup&quot;&gt;&lt;/a&gt;Backup&lt;/h2&gt;&lt;p&gt;从desktop备份数据到NAS，用的是 &lt;a href=&quot;https://kopia.io/&quot;&gt;Kopia&lt;/a&gt;。类似的工具还有 &lt;a href=&quot;https://www.borgbackup.org/&quot;&gt;Borg&lt;/a&gt;，都是deduplication的备份工具，对于多个备份中的相同的数据（基于hash）不会重复传输和存储，可以高效的进行增量备份，节省存储空间。&lt;/p&gt;
&lt;p&gt;Kopia既有命令行也有GUI，我在desktop上安装了KopiaUI，使用相当方便。存储仓库用SFTP指向NAS，在NAS上不需要安装软件，配置好ssh就可以。&lt;/p&gt;
&lt;p&gt;在desktop上，为每个要备份的目录（我这里是对应着BTRFS的subvolume）创建一个policy，policy中可以配置备份保持的周期（每小时&amp;#x2F;每天&amp;#x2F;每周&amp;#x2F;每月&amp;#x2F;每年的分别保存多少份），执行间隔时间，设定是否压缩，等等。&lt;/p&gt;
&lt;p&gt;如果有些文件&amp;#x2F;目录不想备份，可以写到&lt;code&gt;.kopiaignore&lt;/code&gt;文件中，类似于&lt;code&gt;.gitignore&lt;/code&gt;的格式，这也很方便，比起在软件中配置要简单。&lt;/p&gt;
&lt;p&gt;Desktop 上使用 BTRFS，并且已经使用 snapper 定时（每小时）创建 Btrfs snapshot，那最好是能够让备份工具备份最近一个文件系统snapshot，而不是直接备份当前文件，这样的好处是能够保证一个备份中的文件都是处于同一个时刻的，而不是有时间先后。&lt;/p&gt;
&lt;p&gt;Kopia 的policy支持配置 before-snapshot action，正好可以做这个事情。这个 action 的脚本做的事情就是找过要备份的 btrfs subvolume 中最后一个 snapshot 的路径：&lt;/p&gt;
&lt;figure class=&quot;highlight sh&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;snapshots=&lt;span class=&quot;variable&quot;&gt;$KOPIA_SOURCE_PATH&lt;/span&gt;/.snapshots&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;latest=`&lt;span class=&quot;built_in&quot;&gt;ls&lt;/span&gt; -Art &lt;span class=&quot;variable&quot;&gt;$snapshots&lt;/span&gt; | &lt;span class=&quot;built_in&quot;&gt;tail&lt;/span&gt; -n 1`&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;echo&lt;/span&gt; KOPIA_SNAPSHOT_PATH=&lt;span class=&quot;variable&quot;&gt;$snapshots&lt;/span&gt;/&lt;span class=&quot;variable&quot;&gt;$latest&lt;/span&gt;/snapshot&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Kopia 会备份这个 action 返回的 KOPIA_SNAPSHOT_PATH，而不是原始的 KOPIA_SOURCE_PATH。&lt;/p&gt;
&lt;p&gt;以前用MacOS，它的 TimeMachine 很好用的一点是可以在 timeline 中很方便的浏览备份的文件，快速的定位到想要恢复的文件在那个日期的备份中存在。看了一轮 Linux 上的备份工具的介绍，在数据恢复方面都没有能够像 TimeMachine 那么方便的，需要一个个备份去查看。有些工具能够提供文件在不同备份之间 diff 的功能，可惜 Kopia 也没有。&lt;/p&gt;</summary>
    
    
    
    
    <category term="SysAdmin" scheme="http://aleung.github.io/blog/tags/SysAdmin/"/>
    
    <category term="Linux" scheme="http://aleung.github.io/blog/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>K2G 无线路由器</title>
    <link href="http://aleung.github.io/blog/2022/10/09/k2g-router/"/>
    <id>http://aleung.github.io/blog/2022/10/09/k2g-router/</id>
    <published>2022-10-09T15:26:46.000Z</published>
    <updated>2022-12-28T16:11:03.771Z</updated>
    
    <content type="html"><![CDATA[<p>捡到一个斐信K2G无线路由器，上电连接192.168.2.1进入管理界面，发现需要密码，长按后面按钮10秒重置系统，恢复出厂设置。路由器前面只有一盏灯，红色是正在启动，变成橙色是启动完成。</p><p>管理界面的路径包含<code>/cgi-bin/luci</code>，这个路由器的固件应该是基于OpenWRT的。翻了一下菜单，功能不多，看到网上说这个路由器刷机资源挺丰富的，试一试。</p><p>这个路由器硬件是K2G A1版本，固件版本号为22.6.3.20，芯片方案 MT7620A+MT7612EN，64M内存，8MB闪存。</p><h2 id="刷机步骤"><a href="#刷机步骤" class="headerlink" title="刷机步骤"></a>刷机步骤</h2><p>首先刷breed（bootloader），步骤：</p><ol><li><p>从<a href="https://breed.hackpascal.net/">https://breed.hackpascal.net</a>下载breed包(<a href="./breed-mt7620-phicomm-psg1208.bin">breed-mt7620-phicomm-psg1208.bin</a>)到本地电脑。</p></li><li><p>在电脑上运行一个 http server，供下载breed安装包 (例如 <code>npx http-server ~/Download/</code>)</p></li><li><p>电脑用网线连接路由器. 运行<code>ip a</code>确认一下分配到的IP地址，后面路由器要连接这个IP来下载breed安装包</p></li><li><p>进入路由器管理界面（<a href="http://192.168.2.1）：高级设置-系统设置-自动升级-自定义升级时间，从下拉菜单中选择5分钟；按F12打开浏览器开发工具，定位到这个菜单项的HTML，将`value`修改为`05">http://192.168.2.1）：高级设置-系统设置-自动升级-自定义升级时间，从下拉菜单中选择5分钟；按F12打开浏览器开发工具，定位到这个菜单项的HTML，将`value`修改为`05</a> | &#x2F;usr&#x2F;sbin&#x2F;telnetd -l &#x2F;bin&#x2F;login.sh&#96;(注入漏洞)；选择一下其他分钟数，再点回来，确认value内的值没有丢失。然后点击web界面上的保存按钮</p></li><li><p>这时，telnetd应该就运行了。Telnet到192.168.2.1，应该能够进入shell</p></li><li><p>在shell里面执行:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">cd /tmp</span><br><span class="line">wget http://192.168.2.229:8080/breed-mt7620-phicomm-psg1208.bin</span><br><span class="line">mtd unlock Bootloader</span><br><span class="line">mtd -r write breed-mt7620-phicomm-psg1208.bin Bootloader</span><br></pre></td></tr></table></figure></li><li><p>等待路由器重启，breed就已经被刷入</p></li></ol><p>进入breed web界面的方法：</p><ol><li>持续按住路由器上的reset按钮，接通路由器电源，待路由器上电后3-5秒后松开reset按钮，进入bootloader（指示灯会一直红色）</li><li>浏览器访问 <a href="http://192.168.1.1/">http://192.168.1.1</a></li><li>上面可以进行固件备份、固件安装等操作。安装固件和恢复出厂配置时需要选择闪存布局&#x2F;固件类型为”Config区(公版)”。</li><li>开刷前，请必须记得先「恢复出厂设置」-&gt;「Config 区 (公版)」，然后点「执行」</li></ol><p>来源：<a href="https://www.right.com.cn/FORUM/thread-325866-1-1.html">https://www.right.com.cn/FORUM/thread-325866-1-1.html</a></p><h2 id="固件"><a href="#固件" class="headerlink" title="固件"></a>固件</h2><p>然后下一步刷固件，网上搜索发现2020年之后没有人玩这款路由器了，后面没有什么更新，固件都是之前的。</p><h3 id="Padavan（老毛子）固件"><a href="#Padavan（老毛子）固件" class="headerlink" title="Padavan（老毛子）固件"></a>Padavan（老毛子）固件</h3><p>Padavan是俄罗斯（老毛子）大神改自华硕路由器系统。网上的Padavan固件基本上都没有提供K2G的版本，只找到这个提供了K2G：<a href="https://www.right.com.cn/forum/thread-4000461-1-1.html">https://www.right.com.cn/forum/thread-4000461-1-1.html</a></p><p>其中纯净版可以正常使用，但标准版固件有问题，重新启动或恢复出厂设置无法启动路由器。</p><h3 id="Pandorabox-潘多拉固件"><a href="#Pandorabox-潘多拉固件" class="headerlink" title="Pandorabox 潘多拉固件"></a>Pandorabox 潘多拉固件</h3><p>Pandorabox 基于 OpenWRT 修改而来，和 OpenWRT 几乎拥有一样的优缺点，但无线驱动比openwrt的开源驱动好很多。</p><ul><li>胜在丰富的插件和自由的固件定制</li><li>使用复杂</li></ul><p>忘记有没有找到K2G能用的了，反正最后用的是上面那个Padavan纯净版，没有什么插件，作为一个路由器够用了。</p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>书房里的wifi信号不是太好，但又拉不了网线进房间。台式电脑放在书房里，另外又装了台NAS，如果都走无线就太慢了。尝试使用这个K2G作为无线中继，在房间中的设备用网线连上这个中继，效果还可以，速度比电脑自己的无线网卡好。</p><p>Padavan的无线设置里选择无线桥接，工作模式选择“AP-Client + AP“，这时这个无线接口充当另外一个AP的客户端，配置上级AP的SSID和认证密码，就像一个客户端设备一样连上；同时这个无线接口还是一个AP，可以为其他设备提供接入，跟上级AP同一个子网，DHCP也是由上级路由器提供。</p><p>如果不需要为其他无线设备提供接入，只用网线，也可以选择工作模式为 “AP-Client“ only，按理说性能会好些，不过我试了一下同时充当AP速度也没有什么区别。</p><p>无线桥接中的工作模式选择还有 “WDS桥接“ 和 ”WDS中继“ 两种，好像是说要求上下级的路由器的芯片系列相同才行。先试的是WDS模式但没有成功，AP-Client模式能用也就不深究了。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;捡到一个斐信K2G无线路由器，上电连接192.168.2.1进入管理界面，发现需要密码，长按后面按钮10秒重置系统，恢复出厂设置。路由器前面只有一盏灯，红色是正在启动，变成橙色是启动完成。&lt;/p&gt;
&lt;p&gt;管理界面的路径包含&lt;code&gt;/cgi-bin/luci&lt;/code&gt;，这个路由器的固件应该是基于OpenWRT的。翻了一下菜单，功能不多，看到网上说这个路由器刷机资源挺丰富的，试一试。&lt;/p&gt;
&lt;p&gt;这个路由器硬件是K2G A1版本，固件版本号为22.6.3.20，芯片方案 MT7620A+MT7612EN，64M内存，8MB闪存。&lt;/p&gt;
&lt;h2 id=&quot;刷机步骤&quot;&gt;&lt;a href=&quot;#刷机步骤&quot; class=&quot;headerlink&quot; title=&quot;刷机步骤&quot;&gt;&lt;/a&gt;刷机步骤&lt;/h2&gt;&lt;p&gt;首先刷breed（bootloader），步骤：&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>广州COVID-19疫情信息图</title>
    <link href="http://aleung.github.io/blog/2021/06/12/Guangzhou-Covid-19-infograph/"/>
    <id>http://aleung.github.io/blog/2021/06/12/Guangzhou-Covid-19-infograph/</id>
    <published>2021-06-12T14:26:46.000Z</published>
    <updated>2022-08-28T15:12:31.219Z</updated>
    
    <content type="html"><![CDATA[<p>最近在看一本书《<a href="https://book.douban.com/subject/27108685/">用数据讲故事</a>》，介绍用故事思维做数据可视化。看过后想找个东西来练习一下，正好最近广州一波COVID-19瘟疫爆发，就试着做一个疫情的信息图。</p><p>这张图要传达些什么信息呢？我的目标是向关心疫情的普通民众解释：</p><ol><li>哪里出现了病例？</li><li>这些地方的疫情有多严重？</li><li>随着时间过去，情况发生了什么变化？</li><li>从一开始一个阿婆得病，到后来多处爆发，疫情是怎样变得严峻起来的？</li></ol><p>另外，信息图要在社交媒体发布，我设想它应该能在手机上比较轻松的阅读。</p><p><img src="/blog/2021/06/12/Guangzhou-Covid-19-infograph/jun9.png"></p><p><em>6月8日疫情信息图</em></p><span id="more"></span><p><strong>哪里出现了病例？</strong></p><p>随着疫情升温，社交媒体上传播的信息越来越纷杂，有些是不实传闻，有些是将密切接触者&#x2F;此密切接触者也当作出现病例。新闻报道和政府公告中列出的地名并不直观，例如一直说荔湾区出现疫情，我过了好多天才知道其实严重的是芳村而不是老荔湾。有些稍偏僻的地方名称不是所有人都熟知。再加上政府的防疫措施例如局部地区封闭、全民核酸检测等在全市各地都有进行，让很多人没有完全弄清楚出现病例的是哪些地方。</p><p>对于在哪里这个信息，用地图来表示应该是最直观的。</p><p><strong>这些地方的疫情有多严重？</strong></p><p>回答了哪里出现了病例这个问题后，接下来大家很自然的就会想知道有多少个病例，一个地方只有一两个病例和几十个病例的严重性显然是不一样的。另外，网上流传的哪儿有密切接触者的信息让人心惶惶，但密切接触者只是危险因素，已确诊病例（包括无症状感染者）才是更严谨的反映严重性的指标。</p><p>每个地方的数据就是一个数字，最简单和直观的展示方式就是把这个数字标出来。另外，为了直观的对比不同地区的情况，在地图上使用气泡图来反映这个数据。考虑视觉上不要太过繁杂，只使用了4种气泡大小。</p><p><strong>随着时间过去，情况发生了什么变化？</strong></p><p>上面的累计病例数字并不能反映变化的趋势：情况是已经好转，还是还在恶化中？已经层层加码的防疫措施是否起到了作用？</p><p>将单日的确诊数字列出来，就比较好反映趋势了。本来随时间变化的数据最典型是用折线图来表示，但我发现每日确诊数字并没有非常强的连贯性，跳跃比较厉害，可能用柱状图的效果更好一些。为了控制版面，突出重点，省略了病例很少并且最近几天没有新发病例的地区。</p><p><strong>从一开始一个阿婆得病，到后来多处爆发，疫情是怎样变得严峻起来的？</strong></p><p>这是需要更进一步发掘的信息。第一个病例出现时，大家还有点不以为然，到后来多处爆发，防疫措施不断升级，包括带走隔离密切接触者、禁止堂食、突然封闭小区做大规模核酸检测、交通封锁禁止离开等等，让市民都懵了，不知道疫情是怎样突然变得严峻起来的，封区和全民检测这些措施是必要的吗？针对这个问题，可以从很多个角度来分析。在刚开始只有十来个案例的时候，已经有传媒做了流行病学调查的信息图，将每个病例之间传播链展示出来。我不想继续在这个角度去叙述，一方面是公开资料不足够，另外我觉得大众对进一步的传播链分析并不会很感兴趣。但是，大家还是会关心为什么在一个新的地方出现病例：首先这会引起紧张，意味着疫情扩散；另外是新闻效应——原来没有病例现在出现了，肯定是有特别原因的，而已经出现病例的地方传染给家庭成员或者社区中接触的其他人，那就不太奇怪（事实上也是这样）。故此，我选择了这个角度来描述：瘟疫是怎么进入一个地区的，也就是首例的传播途径。</p><p>这个信息也是与地理位置相关的，故此还是在地图上展现，我用了连接图（connection map）来描述这个信息。</p><p><strong>图幅大小与细节</strong></p><p>开始做的时候对这个没有什么概念，先试着把地图底图做出来，再添加部分元素来看看效果。比我想象中好一些，基本跟做PowerPoint slides差不多的图像元素尺寸，在手机上看问题都不大。局部细节可能需要放大一级来看，但不会出现反复拖动看不到全貌的问题。最初担心可以显示的宽度不足，需要用纵向长条的图幅，而因为地图的固有比例，长图会很难排版。最终没有这个担忧，用了A4的比例，一屏就能看全。</p><p>为了能在手机不大的屏幕上容易阅读，尽量缩减了图的复杂性，限制色彩使用的数量。</p><p><strong>统计单元</strong></p><p>这张图的几个信息都涉及到“哪里”，到底应该用什么作为统计的单元呢？最初我的想法是与政府划定的风险区一致，但查看了公告后，发现除了芳村的中风险区是以街道为单位外，其余的都是细化到一栋楼、一个小区。这么小的地理单元在统计上意义不大。</p><p>后来，我决定以<a href="https://zh.wikipedia.org/wiki/%E8%A1%97%E9%81%93%E5%8A%9E%E4%BA%8B%E5%A4%84">街道</a>作为统计单元，主要原因是街道是乡级行政区，在城市管理工作中的角色很重，在当前广州的疫情防控工作中，很多措施都是在街道这个层面上进行的，另外病例所属街道的信息在政府公告上可以查到，有可靠信息源。在核心城区中的街道面积用作病例统计比较适合，不过外围城区的街道面积就要大很多，从图中可以看到可以有十倍以上差别。是否有更好的统计单元？这可能是可以改进的地方。</p><p><strong>反馈及修改</strong></p><p>在6月8日的图上，有病例的街道在地图上用了浅红色覆盖，用意是容易看出街道的地理范围。但正如上面所说，有些街道面积太大，例如番禺的洛浦、大石，只有一两个病例，就把二十多平方公里的地域标红了，容易让读者误解全部都是疫区。在6月9日的图中，做了另外一个尝试：去掉大面积的颜色覆盖，仅保留气泡。对于只有少量病例的街道，气泡并不是标注在地理中心，而是标注在病例出现的大致位置。这样做解决了疫区误解的问题，但比起之前少了街道地理区域的信息，视觉效果上好像也差了一些。</p><p>第一天的图容易误解可能还有一个原因是配色——浅红色让人理解为危险区域。如果改成中性的颜色可能也会好些。在《用数据讲故事》作者的blog中最近一篇文章 <a href="https://www.storytellingwithdata.com/blog/2021/6/8/colors-and-emotions-in-data-visualization">colors and emotions in data visualization</a>，正好就提到了 map of COVID-19 cases 的例子：一个图是用了红色”bloody-red”，另一个图“using more decent colors to avoid an emotion of fear”，选择了蓝-绿色系，两者传递的情感就很不一样。</p><p><img src="/blog/2021/06/12/Guangzhou-Covid-19-infograph/jun10.png"></p><p><em>6月9日疫情信息图</em></p><p>第二天的图还做了少量改进：气泡图用不同颜色添加了额外一个维度的信息——是否在最近3天&#x2F;7天出现新的病例。这样改动后，地图上的信息量增加了，能直观的区分出一个地区的疫情是否已经受控。不过采用的配色并不理想，辨析度不够，还需要改进。</p><p><strong>不足</strong></p><p>一个粗糙的练习作品，做出来的图在美感方面相当欠缺。配色、字体、版面设计，都是将就着做出来。一动手就暴露出自己在这方面没有知识储备，没有经验，胡乱尝试怎么都不顺眼。</p><p>地图上的线条比较多，包括表示传播途径的红色箭头和指示气泡对于街道名称的灰色线，显得有些杂乱，不知道有什么改进的方法。</p><p>最后，也是最核心的，在一张图里展示这几样信息，到底是太多了还是不够？这个故事讲好了吗？受众有没有明确？我所讲的是他们关心的吗？</p><p>这次疫情的数据还可以从很多不同的角度来分析，例如flyisland就做了一个图，分析传播链条中各个节点的R值，从中可以观察到有哪些超级传播者带来影响。可是他的图好像只发布在微信中，没有web上可链接的资源。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近在看一本书《&lt;a href=&quot;https://book.douban.com/subject/27108685/&quot;&gt;用数据讲故事&lt;/a&gt;》，介绍用故事思维做数据可视化。看过后想找个东西来练习一下，正好最近广州一波COVID-19瘟疫爆发，就试着做一个疫情的信息图。&lt;/p&gt;
&lt;p&gt;这张图要传达些什么信息呢？我的目标是向关心疫情的普通民众解释：&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;/ol&gt;
&lt;p&gt;另外，信息图要在社交媒体发布，我设想它应该能在手机上比较轻松的阅读。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/2021/06/12/Guangzhou-Covid-19-infograph/jun9.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;6月8日疫情信息图&lt;/em&gt;&lt;/p&gt;</summary>
    
    
    
    
    <category term="DataVisualization" scheme="http://aleung.github.io/blog/tags/DataVisualization/"/>
    
  </entry>
  
  <entry>
    <title>选择合适的5号/7号电池</title>
    <link href="http://aleung.github.io/blog/2021/05/04/Choose-Cell-Battery/"/>
    <id>http://aleung.github.io/blog/2021/05/04/Choose-Cell-Battery/</id>
    <published>2021-05-03T16:43:42.000Z</published>
    <updated>2022-08-28T15:12:31.219Z</updated>
    
    <content type="html"><![CDATA[<p>虽然现在内置锂电的电子产品越来越多，可是5号、7号电池的使用场景依然不少。现在这种尺寸的电池种类有很多种，包括一次性电池和可充电电池，各自有不同的特性，适合的场景各有不同。</p><h3 id="按图索骥选择电池"><a href="#按图索骥选择电池" class="headerlink" title="按图索骥选择电池"></a>按图索骥选择电池</h3><p>各种电池的特性各异（在下文中有介绍），选择并不容易。根据满足应用场景，尽量低成本的原则，做出下面的判断流程图。其中橙色的线是必须要满足条件才能走的路径，黑色线可以根据偏好而忽略条件判断来选择。可能会有考虑不完善之处，欢迎留言讨论。</p><p><img src="/blog/2021/05/04/Choose-Cell-Battery/flow.png"></p><span id="more"></span><p><em>Diagram source: <a href="https://whimsical.com/ChUoqFHMLZzKPzHiP9F5hq">https://whimsical.com/ChUoqFHMLZzKPzHiP9F5hq</a></em></p><h3 id="电池分类"><a href="#电池分类" class="headerlink" title="电池分类"></a>电池分类</h3><p>同样外形尺寸的电池，不同类型会有不同的编号。要选择电池类型，首先要知道它们的编号。这个<a href="https://www.wikiwand.com/en/List_of_battery_sizes">wikipedia页面</a>列出了各种标准的电池型号和尺寸。</p><ul><li>5号（AA）电池，IEC编号为R6，对应锂电池的外形规格是14500。</li><li>7号（AAA）电池，IEC编号为R03，对应锂电池的外形规格是10440。</li></ul><p>对于5号&#x2F;7号尺寸的电池，现在会用到的有以下种类（不推荐使用的就不列出了）：</p><p>一次性电池</p><ul><li><p>碱性电池：容量在2000mWh左右，缺点是时间长会漏液</p><ul><li>网上推荐品牌：小米&#x2F;紫米彩虹，京东京造，宜家</li></ul></li><li><p>锂铁电池：容量大，4500mWh左右；放电电流大，放电平台高且平稳，自放电极低（2%&#x2F;年）；不漏液，重量轻。价钱稍贵。</p><ul><li>目前主要可以买到的品牌：耐时，小米超级电池</li></ul></li></ul><p>可充电电池：</p><ul><li><p>镍氢电池（NiMH）：典型容量2400mWh&#x2F;960mWh，放电电流大；自放电稍大，不适合过长时间使用，但目前以Eneloop为最出色代表的低自放电电池解决了此问题，实际应用中只建议使用低自放电的；放电平台低（1.2V左右），有些电子产品会因电压太低不能用。</p><ul><li>网上推荐品牌：日本富士通FDK生产白色Eneloop（国内产的是松下evolta技术，性价比低不推荐），紫米1800（松下代工，相当于国内产Eneloop），宜家Ladda（富士通FDK生产，相当于日本产Eneloop pro）</li></ul></li><li><p>锂聚合物（Li-ion）电池，内置稳压电路：容量2900mWh，恒压1.5V，但会造成用电器无法识别低电量（部分产品会在低电量时降压以供检测）；因为降压电路的存在，放电电流不够大，自放电大；网上有人反映寿命不长。目前价格比较贵。</p></li><li><p>镍锌电池：放电电流大，容量2500mWh，放电平台很平稳且极高（1.6～1.7V）；循环次数少寿命短，因放电截止电压太高，在大部分用电器上会过放电而损坏。不推荐使用。</p></li><li><p>磷酸铁锂（LiFePO4）电池：放电电流大；容量低，5号尺寸典型650mAh, 2000mWh（因为要和占位桶搭配使用，相当于1000mWh，不足镍氢40%），7号尺寸只有220mAh，700mAh；放电平台很平稳3.2V，因为电压太高，需要搭配占位桶（假电池）才能用；重量轻。</p><ul><li>目前主要可以买到的品牌：倍量（其他很多品牌声称超过700mAh的都是虚标的）</li></ul></li></ul><p>另外，下面列出常见的可充锂电池。但是可充锂电池一般都是18650尺寸的，电压也高很多，并不能替代5号&#x2F;7号电池。</p><ul><li>IMR 锰酸锂（LiMn）：动力电池，电流大，容量相对小一点</li><li>Li-ion 液态锂离子电池：标称电压3.7V，充电截止电压4.2V；也有高压电池标称3.8V，截止4.35V</li><li>磷酸铁锂（LiFePO4）：标称电压3.2V，充电截止电压3.6V；耐过放电能力相对比其他类型好一点。</li></ul><p>由于锂电池的充电截止电压有几种不同，充电时一定要注意在充电器上选择正确的类型。一般的智能充电器当识别到锂电池时，都是按照缺省4.2V截止，充磷酸铁锂一定要改成3.6V。</p><h2 id="选择的原则"><a href="#选择的原则" class="headerlink" title="选择的原则"></a>选择的原则</h2><p>再看回开头的选择流程图，其实背后基本上是这几个原则：</p><ul><li>如果对价格不敏感，一概选择一次性锂铁电池是最省心的。无论是大电流高耗电，还是低耗电长时间使用都适用。电量也比碱性电池大不少。</li><li>对于低耗电的应用场合，不错的碱性电池大概1元一粒就能买到，其实是很划算的。问题就是碱性电池时间长了就漏液，各种品牌无分贵贱都无一例外。等到发现设备电量低不能工作时已经漏液了，需要隔一段时间就用万用表测量一下电压才保险。</li><li>充电电池，优先选择低自放电的镍氢电池，在容量、性能、价格方面都比较均衡。唯一的问题是电压太低，有些设备用一下就显示没电了，实际上电池的电量还没有用完。在有电动马达的设备中，电压低了也会显得没力。自放电低，也可以用在低耗电长时间运行的场合，例如遥控器、电子钟、电子温度计之类，不过还是得定期更换充电，不能用到没电为止，以免过放电损坏电池。</li><li>磷酸铁锂电池用起来比较麻烦，如果有其他选择尽量不要用它。跟其他类型电池比起来电量偏小，并不耐用。3.2V的电压，如果当成普通电池使用很容易弄坏设备；充电器也需要设置到专门的截止电压，用错会有危险。家里其他人通常不会搞，要说清楚才行。但是，在需要大电流和稳定电压的场景，充电电池中唯有磷酸铁锂才能用。</li><li>锂聚合物降压到1.5V的电池优势不多，目前看来没什么性价比。</li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;虽然现在内置锂电的电子产品越来越多，可是5号、7号电池的使用场景依然不少。现在这种尺寸的电池种类有很多种，包括一次性电池和可充电电池，各自有不同的特性，适合的场景各有不同。&lt;/p&gt;
&lt;h3 id=&quot;按图索骥选择电池&quot;&gt;&lt;a href=&quot;#按图索骥选择电池&quot; class=&quot;headerlink&quot; title=&quot;按图索骥选择电池&quot;&gt;&lt;/a&gt;按图索骥选择电池&lt;/h3&gt;&lt;p&gt;各种电池的特性各异（在下文中有介绍），选择并不容易。根据满足应用场景，尽量低成本的原则，做出下面的判断流程图。其中橙色的线是必须要满足条件才能走的路径，黑色线可以根据偏好而忽略条件判断来选择。可能会有考虑不完善之处，欢迎留言讨论。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/2021/05/04/Choose-Cell-Battery/flow.png&quot;&gt;&lt;/p&gt;</summary>
    
    
    
    
    <category term="gadget" scheme="http://aleung.github.io/blog/tags/gadget/"/>
    
  </entry>
  
  <entry>
    <title>Setup transparent proxy on Ubuntu</title>
    <link href="http://aleung.github.io/blog/2021/02/28/Setup-transparent-proxy-on-Ubuntu/"/>
    <id>http://aleung.github.io/blog/2021/02/28/Setup-transparent-proxy-on-Ubuntu/</id>
    <published>2021-02-28T10:21:21.000Z</published>
    <updated>2022-12-07T15:43:20.744Z</updated>
    
    <content type="html"><![CDATA[<p>家里各种联网的设备多了，要能比较方便连上Internet，还是在路由器上设置个透明代理比起每个设备都要安装简单一些。我的目标是设置多个wifi热点，其中一个配置透明代理，有需要的设备才连上这个热点，这样可以避免在代理出什么问题的时候影响了普通的网络访问。</p><p>最初想直接在路由器上搞，OpenWRT支持配置多个wifi热点多个网络。但安装OpenClash后发现它是要设置iptables规则的，这是透明代理所必须，而它设置的iptables规则对所有网络都生效了，做不到仅对一个热点打开代理。OpenClash用起来似乎还问题多多，怎么配置都不太正常。</p><p>想起来家里还有个9年前的小电脑，用的是上网本的CPU，功耗比较低，虽然安装桌面系统它性能已经完全不能用了，但做这个用途还是足够的。而且它有一个以太网卡和一个无线网卡，正好可以充当一个次级无线路由，挂在主路由下面，物尽其用。翻出来启动一看，系统装的是Lubuntu 18.04 LTS，把软件升级了一下一切正常，那我也懒得重装系统了。</p><p>平常都没有怎么接触Linux系统、网络配置的东西，摸索着把东西装起来。比起网上的好多资料，应该算是比较简化的步骤，尽量沿用原系统缺省安装的软件和配置。整个过程总结如下。</p><span id="more"></span><ul><li><p>首先，开启OS路由功能，修改 <code>/etc/sysctl.conf</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.ip_forward=1</span><br></pre></td></tr></table></figure></li><li><p>安装dnsmasq-base。不要装dnsmasq，因为它会起一个服务，而NetworkManager在设置wifi hotspot后也会运行dnsmasq，造成重复的dnsmasq进程。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt install dnsmasq-base</span><br></pre></td></tr></table></figure></li><li><p>如果系统里面已经有 systemd-resolved 服务，需要把它关闭，否则53端口会冲突。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl disable systemd-resolved</span><br></pre></td></tr></table></figure></li><li><p>Ubuntu的网络设备和连接是用NetworkManager进行管理的，它会自动覆盖重写<code>/etc/resolve.conf</code>文件。修改NetworkManager配置 <code>/etc/NetworkManager/NetworkManager.conf</code>，让它把<code>/etc/resolve.conf</code>里的nameserver配置指向dnsmasq。另外，如果 <code>/etc/resolve.conf</code> 是一个已经存在的链接，需要删除它。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[main]</span><br><span class="line">dns=dnsmasq</span><br></pre></td></tr></table></figure></li><li><p>运行NetworkManager的命令行交互式配置工具<code>nmtui</code>，创建一个wifi hotspot：mode选择access point，IPv4 config选择shared。如果不是远程登录，也可以直接在GUI上点击系统托盘中网络图标来配置。</p><ul><li>创建出来的子网是 <code>10.42.0.1/24</code>，而不是常见的 <code>192.168.*.*</code>。但反正能用，有什么关系呢。</li><li>NetworkManager会自动启动dnsmasq，DHCP也自动配置好了，无需安装任何额外软件包。</li></ul></li><li><p>安装clash。Clash没有安装包，就一个可执行文件。为它创建专门的clash用户。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">useradd -U clash</span><br><span class="line">copy clash /usr/local/bin</span><br><span class="line">chown clash:clash /usr/local/bin/clash</span><br></pre></td></tr></table></figure></li><li><p>配置<code>/etc/clash/config.yaml</code>，配置方式细节参考<a href="https://github.com/Dreamacro/clash/wiki/configuration">clash文档</a>和<a href="https://lancellc.gitbook.io/clash/">gitbook</a>，下面主要强调我的配置方式：</p><ul><li>DNS设置为fake-ip模式。关于DNS工作模式，这篇文章解释得比较清楚： <a href="https://blog.skk.moe/post/what-happend-to-dns-in-proxy/">https://blog.skk.moe/post/what-happend-to-dns-in-proxy/</a></li><li>将DNS侦听在1053端口，以免与dnsmasq的DNS服务冲突。<br>Dnsmasq的DNS cache功能实际上不会被使用，因为往53端口流量会被iptables重定向到了clash的1053端口。但是如果关闭了dnsmasq的DNS功能，DHCP就不会下发nameserver配置给DHCP client，造成连上热点的设备没有DNS配置。</li></ul></li><li><p>设置iptables，让clash工作为透明代理。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">DNS rules</span></span><br><span class="line">iptables -t nat -I PREROUTING -p udp --dport 53 -j REDIRECT --to 1053</span><br><span class="line">iptables -t nat -I OUTPUT -p udp -d 127.0.0.0/8 --dport 53 -j REDIRECT --to 1053</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">All incoming TCP traffic except SSH redirect to Clash port</span></span><br><span class="line">iptables -t nat -A PREROUTING -p tcp ! --dport 22 -j REDIRECT --to-ports 7892</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">All outgoing TCP traffic except from user transmission and clash redirect to Clash port</span></span><br><span class="line">iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner debian-transmission -j ACCEPT</span><br><span class="line">iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner clash -j ACCEPT</span><br><span class="line">iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-port 7892</span><br></pre></td></tr></table></figure><ul><li>首先需要复习一下关于iptables的知识：<a href="https://www.cnblogs.com/f-ck-need-u/p/7397146.html">https://www.cnblogs.com/f-ck-need-u/p/7397146.html</a></li><li>我这里采用了一个比较简化的配置，规则很少。没有支持UDP代理，目前感觉没有这样的需求，迟些再看看有没有必要加上吧。</li><li>Clash配置中设置 redire-port 7892，用iptable REDIRECT target将包转发到此端口。<ul><li>另外还有一种模式为TPROXY，参考 <a href="https://gsoc-blog.ecklm.com/iptables-redirect-vs.-dnat-vs.-tproxy">Iptables REDIRECT vs. DNAT vs. TPROXY</a> 和 <a href="https://www.bilibili.com/read/cv14088928">使用 iptables 的 tproxy</a>。TPROXY只能工作于PREROUTING链，不工作于OUTPUT链，为了让本机对外流量进入代理需要配置路由规则，整个配置更复杂一些。</li></ul></li><li>支持代理wifi hotspot流量（PREROUTING链）和本机流量（OUTPUT链）。</li><li>使用了iptables的owner模块功能，配合专门的clash用户，避免流量回环。</li><li>来自transmission的流量（BT）也要bypass。</li></ul></li><li><p>将iptables规则持久化，下次启动的时候自动恢复。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">apt install iptables-persistent</span><br><span class="line">iptables-save &gt; /etc/iptables/rules.v4</span><br></pre></td></tr></table></figure></li><li><p>创建clash服务：<code>/etc/systemd/system/clash.service</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">[Unit]</span><br><span class="line">Description=Clash Daemon</span><br><span class="line">After=network.target</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">Type=simple</span><br><span class="line">StandardError=journal</span><br><span class="line">User=clash</span><br><span class="line">Group=clash</span><br><span class="line">AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN</span><br><span class="line">ExecStart=/usr/local/bin/clash -d /etc/clash/</span><br><span class="line">Restart=on-failure</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy=multi-user.target</span><br></pre></td></tr></table></figure><ul><li>其中 <code>AmbientCapabilities</code> 那行是因为以非root用户运行，需要赋予权限。其实在我的这个场景下不要也行：DNS绑定的是高位端口，不是53端口，不需要<code>CAP_NET_BIND_SERVICE</code>；不代理UDP没有 <code>CAP_NET_ADMIN</code> 也无所谓。</li></ul></li><li><p>最后启动 clash 服务</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">systemctl daemon-reload</span><br><span class="line">systemctl enable clash</span><br></pre></td></tr></table></figure></li><li><p>装个 <a href="https://github.com/haishanh/yacd">yacd</a> 作为clash的web dashboard，管理起来会方便很多</p><ul><li><p>解压到 <code>/etc/clash/dashboard</code> 目录下面，并且在 <code>/etc/clash/config.yaml</code> 里面配置一行就行了</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">external-ui: dashboard</span><br></pre></td></tr></table></figure></li><li><p>浏览器访问 <code>http://&lt;router_ip&gt;:9090/ui</code> 打开</p></li></ul></li></ul><hr><p>Update：</p><p>后来发现这个小电脑的wifi热点速度很慢，应该是无线网卡的问题。于是还是改回旁路由的模式。</p><p><img src="https://kroki.io/nwdiag/svg/eNqFkNGOgjAQRd_9igk-i6VujIb4JcaYSisQS6dpQdhs_Pe1RcUIjW_N9N577oxqecly-JsBlFbD3hZMC9hBJrHhh_QxXiygQi6MSmf3iRJ1i-YCBVbCOwEY50ZYezdGyZbGyXoTJzFZ0p_If_dm2E_KkujgRaiFak3tmLd3jDbY_YY5q7hzHN_sFTKJWjlU6mWZZLYIiOhTVF-PJ-xGfSRT4Takb_O1Cxm6VHgqpejfOhvh8kbYOgykz_W_EelA9JHHget5ucFGPzgZSjTOPGfkfGYkerva522CC0wHurhX4LjI7R86wLAH"></p><p>在主路由器OpenWRT上多配置了一个名为proxy的子网（bridge interface），关联了一个LAN口（要设置VLAN与其他口隔离）和一个wifi AP。通过在DHCP Server上设置DHCP-Options，把这个proxy子网的DNS和网关都需要指向安装了clash的小电脑。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">config dhcp &#x27;proxy&#x27;</span><br><span class="line">option start &#x27;100&#x27;</span><br><span class="line">option limit &#x27;150&#x27;</span><br><span class="line">option interface &#x27;proxy&#x27;</span><br><span class="line">list dhcp_option &#x27;3,192.168.3.2&#x27;</span><br><span class="line">list dhcp_option &#x27;6,192.168.3.2&#x27;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;家里各种联网的设备多了，要能比较方便连上Internet，还是在路由器上设置个透明代理比起每个设备都要安装简单一些。我的目标是设置多个wifi热点，其中一个配置透明代理，有需要的设备才连上这个热点，这样可以避免在代理出什么问题的时候影响了普通的网络访问。&lt;/p&gt;
&lt;p&gt;最初想直接在路由器上搞，OpenWRT支持配置多个wifi热点多个网络。但安装OpenClash后发现它是要设置iptables规则的，这是透明代理所必须，而它设置的iptables规则对所有网络都生效了，做不到仅对一个热点打开代理。OpenClash用起来似乎还问题多多，怎么配置都不太正常。&lt;/p&gt;
&lt;p&gt;想起来家里还有个9年前的小电脑，用的是上网本的CPU，功耗比较低，虽然安装桌面系统它性能已经完全不能用了，但做这个用途还是足够的。而且它有一个以太网卡和一个无线网卡，正好可以充当一个次级无线路由，挂在主路由下面，物尽其用。翻出来启动一看，系统装的是Lubuntu 18.04 LTS，把软件升级了一下一切正常，那我也懒得重装系统了。&lt;/p&gt;
&lt;p&gt;平常都没有怎么接触Linux系统、网络配置的东西，摸索着把东西装起来。比起网上的好多资料，应该算是比较简化的步骤，尽量沿用原系统缺省安装的软件和配置。整个过程总结如下。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Anti-GFW" scheme="http://aleung.github.io/blog/tags/Anti-GFW/"/>
    
  </entry>
  
  <entry>
    <title>Precisely model delta printer bed shape</title>
    <link href="http://aleung.github.io/blog/2020/04/05/Precisely-model-delta-printer-bed-shape/"/>
    <id>http://aleung.github.io/blog/2020/04/05/Precisely-model-delta-printer-bed-shape/</id>
    <published>2020-04-05T07:38:40.000Z</published>
    <updated>2022-08-28T15:12:31.211Z</updated>
    
    <content type="html"><![CDATA[<p>In firmware, the printable area of delta printer is set to be a circular area. But you can’t set the printing area diameter as large as the printer head reachable area (yellow circle), because the effector and fan need extra space. You have to set a smaller diameter (blue circle) to avoid impacting the belt. (Image originates from <a href="https://reprap.org/wiki/Delta_geometry">reprap</a>)</p><p><img src="/blog/2020/04/05/Precisely-model-delta-printer-bed-shape/delta_reachable_area.png"></p><p>How can I make use of most of the printer head reachable area as printable area? Slicer software e.g. Slic3r&#x2F;PrusaSlicer has feature to set bed shape from a STL file. It allows you to set bed shape in any irregular shape, and it will alert you if the part is placed outside the printable area. In this way you can set a large printing area diameter in printer firmware, and exclude the areas where interfering will happen inside slicer bed shape setting.</p><p>I used Fusion 360 to model the real printable area of my Kossel Mini printer. The print bed glass is 200mm diameter, so I first drew a 190mm diameter circle as the basic printable area. It’s also set into the firmware.</p><p>I connected the printing host software on PC to my printer and sent G-code to find out the edge positions of the printable area.</p><p><img src="/blog/2020/04/05/Precisely-model-delta-printer-bed-shape/calibration.jpg"></p><p>The orange areas are too close to the tower, where the effector will impact the belt. Yellow areas need to be excluded as well. In those areas, the fan interferes with the belt.</p><p><img src="/blog/2020/04/05/Precisely-model-delta-printer-bed-shape/design.png"></p><p>After the sketch is ready, extrude the printable areas 1mm (the height doesn’t matter) to make a body. Export it into .STL file then load it into bed shape setting of the Slic3r software.</p><p><img src="/blog/2020/04/05/Precisely-model-delta-printer-bed-shape/fusion360.png"></p><p>I also captured the sketch into a .png file and loaded it as texture of printer bed into Slic3r. Now in Slic3r viewer it shows the printer bed shape with grid and dimensions. It’s handy, isn’t it?</p><p><img src="/blog/2020/04/05/Precisely-model-delta-printer-bed-shape/slic3r.png"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;In firmware, the printable area of delta printer is set to be a circular area. But you can’t set the printing area diameter as large as the printer head reachable area (yellow circle), because the effector and fan need extra space. You have to set a smaller diameter (blue circle) to avoid impacting the belt. (Image originates from &lt;a href=&quot;https://reprap.org/wiki/Delta_geometry&quot;&gt;reprap&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog/2020/04/05/Precisely-model-delta-printer-bed-shape/delta_reachable_area.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;How can I make use of most of the printer head reachable area as printable area? Slicer software e.g. Slic3r&amp;#x2F;PrusaSlicer has feature to set bed shape from a STL file. It allows you to set bed shape in any irregular shape, and it will alert you if the part is placed outside the printable area. In this way you can set a large printing area diameter in printer firmware, and exclude the areas where interfering will happen inside slicer bed shape setting.&lt;/p&gt;
&lt;p&gt;I used Fusion 360 to model the real printable area of my Kossel Mini printer. The print bed glass is 200mm diameter, so I first drew a 190mm diameter circle as the basic printable area. It’s also set into the firmware.&lt;/p&gt;
&lt;p&gt;I connected the printing host software on PC to my printer and sent G-code to find out the edge positions of the printable area.&lt;/p&gt;</summary>
    
    
    
    
    <category term="Maker" scheme="http://aleung.github.io/blog/tags/Maker/"/>
    
  </entry>
  
  <entry>
    <title>3D打印机使用新进展</title>
    <link href="http://aleung.github.io/blog/2020/03/08/update-about-my-3d-printer/"/>
    <id>http://aleung.github.io/blog/2020/03/08/update-about-my-3d-printer/</id>
    <published>2020-03-08T14:29:29.000Z</published>
    <updated>2022-08-28T15:12:31.203Z</updated>
    
    <content type="html"><![CDATA[<p>3D 打印机玩了两年多，最近两个月折腾了一番，又有一些新的心得。</p><span id="more"></span><h2 id="软件"><a href="#软件" class="headerlink" title="软件"></a>软件</h2><h3 id="建模软件：Fusion-360"><a href="#建模软件：Fusion-360" class="headerlink" title="建模软件：Fusion 360"></a>建模软件：Fusion 360</h3><p>不知道是不是因为很久以前玩过的类似软件不好用，一直对用鼠标绘图的建模软件敬而远之，因此用像写程序一样的建模语言来做模型。之前就写过我使用过的 <a href="/blog/2018/08/26/3D-modeling-language/" title="3D modeling language">3D modeling language</a>，其中主要用的是 Relativity SCAD，玩了一年半，算比较熟手了，大部分模型都是用它来做的。</p><p>但是建模语言也有一些缺点：</p><ul><li>不直观。需要先在纸上画草图，当时做的时候还好，脑子里能够将代码和图对得上，可是过了几个月回头想修改的时候，就要花一些时间来理解代码了。</li><li>所有东西都要由球体、立方体、圆柱体三种基础集合体组合而成，曲面特别难做，只能勉强用hull解决一部分。想把过渡面弄平滑基本上搞不掂，增加个支撑肋条都麻烦得要命。</li><li>总有各种小问题需要解决</li></ul><p>有一次在 YouTube 上看了一个视频介绍用 Autodesk Fusion 360 来做打印模型的受力分析，就安装了 Fusion 360 来试了一下，对基础操作了解之后，发现使用并不复杂，再看了一些介绍的视频，对这种基于绘图的建模软件的观感完全改变了：</p><ul><li>它同样可以跟建模语言一样做参数化设计，随时可以修改参数去改变模型尺寸。</li><li>通过创建多个参考面画平面图来构建出立体模型，十分直观。</li><li>通过底下的修改历史进度条，随时可以独立修改之前的某个操作，或者在之前某个步骤的位置插入额外的操作。并不是想我以前以为那样很难修改以前画好的东西。</li><li>过渡面、斜切、肋条这些细节修改做起来轻而易举。</li></ul><p>在 YouTube 上有不少很好的教学视频，边看边试，做了几个模型后基本上也能上手了，一下子就变成了我现在建模用的主力工具。Fusion 360 对个人使用提供免费无限期许可，对于功能这么强大的软件真是很不错。</p><p><img src="/blog/2020/03/08/update-about-my-3d-printer/fusion360.png"></p><h3 id="切片软件：Slic3r-PrusaSlicer-Slic3r"><a href="#切片软件：Slic3r-PrusaSlicer-Slic3r" class="headerlink" title="切片软件：Slic3r - PrusaSlicer - Slic3r++"></a>切片软件：Slic3r - PrusaSlicer - Slic3r++</h3><p>自从做好3D打印机以来一直用的是开源的 <a href="https://slic3r.org/">Slic3r</a>，它一直也没有怎么出过升级版本。直到最近两个月，我才知道 Slic3r 有一个fork叫做 <a href="https://www.prusa3d.com/prusaslicer/">PrusaSlicer</a>，在 Slic3r 基础上增加了更多的功能。在 Reddit 上有人评价：完全找不到理由从 PrusaSlicer 转回 Slic3r。既然如此，就下载下来一试，果然：Slic3r的配置选项本来已经多得眼花缭乱了，PrusaSlicer又增加了一堆。然后过了两个星期，再发现了另外一个fork：<a href="https://github.com/supermerill/slic3r">Slic3r++</a>，这虽然好像只是一个个人的项目，但它在PrusaSlicer的基础上增加了几个很好的改进：</p><ul><li>Ironing：在打印完上表面后，用打印头在上面重新再“熨”一遍，将纹理抹平。打印出来的表面平滑了很多，效果相当理想。</li><li>Denser infill：有了这个功能，内部填充密度可以低至4%，上表面只打2实心层，也不怎么影响上表面的封口质量。</li><li>打印 overhang 和 bridge 更容易一些，优化了打印路径。不过，我也观察到打印桥的时候曾经出现不合理的路径，还有需要改进的地方。</li></ul><p>它还有其他一些小改进，但就上面几个就足以让我切换到 Slic3r++ 上来了。</p><h2 id="打印机硬件"><a href="#打印机硬件" class="headerlink" title="打印机硬件"></a>打印机硬件</h2><h3 id="尖头喷嘴与非平面打印（non-planar）"><a href="#尖头喷嘴与非平面打印（non-planar）" class="headerlink" title="尖头喷嘴与非平面打印（non-planar）"></a>尖头喷嘴与非平面打印（non-planar）</h3><p>在YouTube看到一个介绍<a href="https://youtu.be/gmePlcU0TRw">非平面打印</a>的视频，想尝试一下，就买了一个尖头的打印喷嘴。</p><p>先说什么是非平面打印。FDM（熔融沉积）打印是在Z轴上一层一层的把打印材料堆积起来的，打完一层后打印头上升一层高度，再打一层。典型一层的高度是0.3mm。对于打印比较平缓的斜面时，就会有明显等高线阶梯，表面不精细。但实际上打印头是可以在Z轴上运动的，层并不一定需要为水平面。Non-plannar slicing 是通过一定的算法，让打印头在打印模型上表面时，打印层为一个曲面，减少等高线阶梯。这个思路在几年前已经在网上有描述，去年一位汉堡大学的学生将这作为他的毕业论文课题，并且基于开源Slic3r开发了一个基本可用的<a href="https://github.com/Zip-o-mat/Slic3r/tree/nonplanar">实现</a>.</p><p><img src="/blog/2020/03/08/update-about-my-3d-printer/nonplanar.png"></p><p>当打印层不在一个平面上时，打印到凹陷的地方时，打印头上面的部件就有可能碰撞到已打印的凸起位置。因此打印头突出得越多，能够打印的曲面倾斜度越大。</p><p>淘宝上看到有卖这种不锈钢尖头的打印喷嘴。这应该是转接了绘画喷笔的喷头，在国外网站也是早年有讨论，现在国内都量产了。比起E3D喷嘴，这种喷嘴的净空高度增加了好几毫米。</p><p><img src="/blog/2020/03/08/update-about-my-3d-printer/airbrush.jpg"></p><p>可是，换上这个喷嘴后，立马就堵丝了。检查发现应该是因为温度的问题：尖嘴处的热容量小，再加上风扇吹着，温度下降得比较快，打印材料冷却了就堵住了。升高打印温度解决了堵头问题，但多番调试始终打印效果都不理想，出现各种问题，后来就换回了普通喷嘴。</p><p>现在复盘一下，主要应该是这些问题：</p><ul><li>原来用的是带铁氟龙内衬的喉管，上面还是有个接缝。不知道是因为用的时间长了接缝变大，还是什么原因，材料冷却后会卡在接缝处。后来我更换了直通的喉管，进料铁氟龙管一直伸到加热块前面，中间没有任何接缝。</li><li>调平探头的海绵套不适配尖头外形，不能在上面卡住。我做的临时固定措施不牢固，造成自动调平的误差大，所以打印首层不能贴合打印床。</li><li>需要调整打印工件降温风扇的风量的吹风位置，让出料温度合适，既要保持熔融状态顺畅挤出，又要能够在挤出之后迅速冷却。怎样才是最佳的还有待实践。</li></ul><p>结果就是根本没有机会尝试到非平面打印。不过暂时也先不折腾了，更换喷嘴太麻烦。毕竟现在唯一能支持这个功能的slicer还是试验性的，日常应用还是有不少限制，等什么时候软件完善了再来试试。</p><h3 id="风扇"><a href="#风扇" class="headerlink" title="风扇"></a>风扇</h3><p>在试验尖头喷嘴的时候感觉风扇需要调整一下，于是将二合一散热风道的设计又重做了几个版本。</p><p><img src="/blog/2020/03/08/update-about-my-3d-printer/2020-02-b-1.png"></p><p>尝试了不同的风量比例、出风口大小、角度、吹风位置，总结了一些经验：</p><ul><li>轴流风扇的风压小，风道出风口不能收得太小，越直越好，否则出风量很小，空气甚至会在风扇处倒灌。如果换成用径流风扇（涡轮风扇）做工件散热应该会好些，但这样就得装两个风扇了，增加打印头的体积和重量。</li><li>向工件吹风的目的是要将喷嘴挤出的材料在刚挤出的瞬间就迅速冷却，一方面是打印悬空结构时让挤出材料不下垂，另一方面（可能）有助于减少后续冷却带来的收缩。因此，出风方向应该正对着喷嘴尖端，让挤出处的风量最大，散热效果最好。而对于已经打印出来的工件，</li><li>观察到打印桥的时候风力似乎会将拉丝往下压，造成桥的下垂，因此可能出风角度尽量接近水平一些有好处（不能肯定）。</li></ul><h2 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h2><p>更换元件，调试过程中间出过好几个问题，一度打印效果非常差。以下是遇到的几个问题，解决后基本上打印质量比较理想了。</p><h3 id="首层裂开上翘"><a href="#首层裂开上翘" class="headerlink" title="首层裂开上翘"></a>首层裂开上翘</h3><p>这个问题在更换风扇风道后出现，还去 StackExchange 上提过<a href="https://3dprinting.stackexchange.com/q/11800/162">问题</a>。打印首层过程中，中间裂开翘起，但是边缘贴合打印床又没有问题。尝试过清洁玻璃板、更换胶水、delta参数校准、Z校准都不起作用，最后仔细观察打印过程，发现打印丝刚挤出来的时候是贴合玻璃板的，然后它就被扯起来了，是因为前面已打印的相邻材料在冷却收缩。首层打印时关闭风扇，就不再出现这个问题。</p><p><img src="/blog/2020/03/08/update-about-my-3d-printer/shrink.jpg"></p><p>但是，我以前一直都是打印全程风扇全开的，也没有出现过这个问题。其实是因为吹风位置改变引起的，没有及时冷却刚挤出来的材料，而是变成冷却已经打印成型的模型，造成模型的收缩。后来调整吹风位置，直接吹向喷嘴尖端就好了（见上面风扇一节）。</p><h3 id="挤出不足"><a href="#挤出不足" class="headerlink" title="挤出不足"></a>挤出不足</h3><p>这个问题是在尝试0.2mm层高时发现的，起初认为是slicer软件问题，在github上提了issue，作者说我的0.3mm层高打印（右侧）其实也是挤出不足的。</p><p><img src="/blog/2020/03/08/update-about-my-3d-printer/under_extrude.jpg"></p><p>自从打印机装好以来，一直没有校准过挤出量，感觉上差不多。固件里的挤出参数是根据步进电机和齿轮参数算出来的，想着应该没错吧？</p><p>这次就认真的测量了一下：用笔在打印丝上做记号，发指令让挤出机慢速走个50mm左右，测量实际上打印丝的移动距离。惊讶的发现居然欠了15%左右，怪不得挤出不足。按实测比例修改参数，反复测量两次，大概就校准了。这时0.3mm层高的打印饱满了很多，打印0.8mm薄壁测试，用游标卡尺测量，厚度也误差很少了。</p><h3 id="Z-offset"><a href="#Z-offset" class="headerlink" title="Z offset"></a>Z offset</h3><p>挤出电机的系数校准后，0.2mm层高的首层依然空隙很大，没有什么变化。后来发现是打印头的Z位置不准，Z&#x3D;0 时与打印平台有零点几毫米的空隙，因此首层实际高度高了许多，自然挤出量就不够了。重新做 delta calibration 之后解决问题。</p><h3 id="最大挤出速度"><a href="#最大挤出速度" class="headerlink" title="最大挤出速度"></a>最大挤出速度</h3><p>打印速度可以去到多高？Delta结构的打印机，打印头移动速度在XYZ坐便轴上都可以相当快，但是这只是移动的速度，实际打印时限制打印速度的还有挤出的速度，如果材料的挤出跟不上，打印头移动得过快只会造成欠料，质量降低。而挤出速度也不是仅仅由挤出机决定的，挤出机很够力，但加热块的热量不足以融化大量材料，也挤不出来。</p><p>测试最大挤出速度，同样需要在打印丝上做标记。按照平常使用的打印头加热温度，每次挤出50mm，看看挤出过程中是否有停顿（打滑），寻找最快的速度。测试结果是180度时，最大挤出速度为 4mm&#x2F;s（F240）。</p><p>材料的长度可以换算为体积，对于1.75mm的材料，每毫米体积约为 2.4立方毫米。对于测试到的最大挤出速度，也就是最大挤出体积为 9.6mm^3&#x2F;s。</p><p>实际打印时，因为挤出后空间限制，造成backpressure，特别是打印填充时，挤出速度远小于理想速度。根据<a href="http://www.soliforum.com/topic/15529/finding-maximum-print-speeds/">这里</a>的测试，不同层高的实际挤出速度相对于理想速度的比例为： 0.1mm 38%，0.2mm 61%，0.3mm 92%。这个具体数值肯定会受很多因素的影响，我也懒得测试了，留个安全系数应该差不多。</p><p>在PrusaSlicer里面，可在 Filament Settings -&gt; Advanced -&gt; Print speed override -&gt; Max volumetric speed 设置挤出速度限制，以限制最高打印速度不超出挤出可承受速度范围。根据上面计算，0.3mm层高时不应大于 8.8mm^3&#x2F;s ，0.2mm层高时不大于 5.8。考虑增加85%的安全系数，设置成 7.5 和 4.9 比较合适。目前还不知道冷却风扇对实际挤出时的最大速度会带来多大影响。</p><h2 id="Marlin-Firmware"><a href="#Marlin-Firmware" class="headerlink" title="Marlin Firmware"></a>Marlin Firmware</h2><p>最近也升级了打印机的固件。<a href="https://marlinfw.org/">Marlin Firmware</a> 在长长的开发周期之后，终于正式发布了2.0版本。2.0版本主要是32bit，增加了对很多控制板的支持，但对于我用的低端 Arduino，其实没有什么区别。装2.0试了一下，感觉有点不是很稳定，不记得具体啥问题了，反正换回了1.1。惊喜的发现，其实2.0里面见到的新增功能，在1.1分支的最新版本里面都已经有了。</p><h3 id="加载-x2F-卸载打印材料"><a href="#加载-x2F-卸载打印材料" class="headerlink" title="加载&#x2F;卸载打印材料"></a>加载&#x2F;卸载打印材料</h3><p>升级固件后，最大的好处是装载和卸下打印丝方便多了。因为是远程送料，以前每次都要先预热打印头，等到温度够了后，手工把打印丝穿过挤出机，一直插到打印头，有融化的材料挤出来为止。要卸下打印丝也一样，预热温度够了手工抽出来。现在菜单里面就有 load&#x2F;unload 选项，固件里配置好参数，菜单一选，就自动预热，自动进&#x2F;退料到位。</p><h3 id="暂停"><a href="#暂停" class="headerlink" title="暂停"></a>暂停</h3><p>另外一个功能是可以在打印过程中暂停，暂停可以有两种用途：更换打印材料（换颜色）；往打印工件内部空腔放入东西（例如一块磁铁），恢复打印后就可以将上面封口，将物品封闭在打印件内部。</p><p><img src="/blog/2020/03/08/update-about-my-3d-printer/embed.jpg"></p><p>这需要在slicer中配合，往预定的层上插入M25（暂停打印）指令，打印到那个层，打印就会暂停，并且打印头移到一个安全位置。这时可以卸下原来的打印丝，换上另外一盘不同颜色的，然后菜单选择继续打印，它就会回到原来位置继续。</p><h3 id="Advanced-linear"><a href="#Advanced-linear" class="headerlink" title="Advanced linear"></a>Advanced linear</h3><p>按<a href="https://youtu.be/n3yK0lJ8TWM">介绍</a>，这个<a href="https://marlinfw.org/docs/features/lin_advance.html">功能</a>能够改善打印速度变化时出料不均匀的问题，不需要各种高级的回抽的设定。可是当时我测试的效果并不好，后来把它关闭了。现在看来，是因为测试前并没有先校准好挤出量，当时是出于挤出不足的状态的。以后有空可以再试一次，看看效果如何。</p><h2 id="YouTube"><a href="#YouTube" class="headerlink" title="YouTube"></a>YouTube</h2><p>前面提到，关于3D打印的很多新了解我都是从 YouTube 上面看到的，上面的关于3D打印、机械、设计方面的视频让人大开眼界。其中，对于3D打印尤其推荐一个大神 <a href="https://www.youtube.com/channel/UCiczXOhGpvoQGhOL16EZiTg">CNC Kitchen</a>，他制作了很多视频，每集关注一个主题，测试不同的打印参数、材料、后处理方式对打印效果的影响。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;3D 打印机玩了两年多，最近两个月折腾了一番，又有一些新的心得。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Maker" scheme="http://aleung.github.io/blog/tags/Maker/"/>
    
  </entry>
  
  <entry>
    <title>Garmin Vovoactive 3</title>
    <link href="http://aleung.github.io/blog/2019/08/25/Garmin-Vivoactive-3/"/>
    <id>http://aleung.github.io/blog/2019/08/25/Garmin-Vivoactive-3/</id>
    <published>2019-08-25T06:07:12.000Z</published>
    <updated>2022-08-28T15:12:31.199Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/blog/2019/08/25/Garmin-Vivoactive-3/vivoactive3.jpg"></p><p>最近将 <a href="/blog/2014/03/15/pebble/" title="Pebble">Pebble Time</a> 手表换掉了，新的手表是 Garmin 的运动手表 <a href="https://www.garmin.com.cn/products/sports-recreation/vivoactive3pvd/">Vivoactive 3</a> (VA3)。</p><span id="more"></span><h2 id="功能考虑"><a href="#功能考虑" class="headerlink" title="功能考虑"></a>功能考虑</h2><p>Vivoactive系列的定位是休闲运动，支持的运动模式特别多，但是没有特别专业的功能，以跟其他主打专业的手表型号形成区分。从传感器来看挺齐全的，除了GPS、加速度、电子罗盘、心率这些基本的，还有温度、气压传感器。其实硬件多一点成本差不了多少，功能之间的区别更多是软件上的。</p><p>这款手表有几个型号：</p><ul><li>标准版本名称没有任何后缀</li><li>Music版增加了音乐播放器功能，可以连接蓝牙耳机</li><li>Trainer版是简化版本，少了支付功能，少了侧滑控制</li></ul><p>我的运动主要是跑步、徒步，偶然游泳和自行车，需要的运动记录功能和信息这款手表都能够提供了。VA3没有户外徒步的运动模式，但是安装 ConnectIQ 的 app 和 data field 可以满足要求。</p><p>原来的 Pebble &#x2F; Pebble Time 作为日常佩戴，除了时间外最主要的使用的几个功能包括：手机来电和消息推送的显示，手机音乐播放器的控制，睡眠记录。确认过 Garmin 的手表都能支持这些功能，一个手表就能同时满足日常佩戴和运动的需要了，不需要运动时再换表，方便一些。</p><p>Garmin Pay 关联银联卡后，可以用手表闪付。我一向跑步不带手机，现在多个支付手段也好，可是支持闪付的店铺太少，还是用处不大。</p><p>VA3只有一个物理按键，大部分操作都要靠触摸屏，专业表就不会这样设计，在运动过程中的操作还是物理按键更高效。它还有个侧滑控制，但用起来很不顺手，也不是太灵敏，后来我就把它关掉了。</p><p>看起来，trainer少了的两大功能也都不是很重要。</p><h2 id="耗电测试"><a href="#耗电测试" class="headerlink" title="耗电测试"></a>耗电测试</h2><p>日常活动跟踪（心率、计步等）功能都打开的前提下做了一系列耗电量测试：</p><ul><li>不停的操作手表，没有开GPS：7.5%&#x2F;h </li><li>平常佩戴 &#x2F; 睡眠，无操作：0.5%&#x2F;h </li><li>打开GPS运动模式，不使用Connect IQ data field，频繁查看手表：&lt;10%&#x2F;h </li><li>使用HoneyHike data field：15.9%&#x2F;h，暂停时～13%&#x2F;h </li><li>运动时使用了 Connect IQ 的field会比内置的耗电，耗电量与field是否处于显示状态无关，即使 field 不在当前显示页面中，也一样耗电。</li></ul><p>不运动的话，日常佩戴大概一个星期看起来是没有问题的。运动模式下GPS打开时耗电量就有点大了，例如跑步可能勉强9个小时左右，我不跑长距离越野跑，应付一个马拉松是绰绰有余了。</p><p>现在感觉最不足的地方是徒步时的电量续航：目前找到最适合徒步应用的 ConnectIQ data field 是 HoneyHike，但它的耗电比较大，续航时间不足6小时。轻量级的徒步往往也会超过6小时，必须要中途休息时充电才行。估计这也是这款手表不提供 hiking 模式的原因。</p><h2 id="ConnectIQ"><a href="#ConnectIQ" class="headerlink" title="ConnectIQ"></a>ConnectIQ</h2><p>应该说支持 ConnectIQ 的手表才能勉强算得上智能手表。Garmin提供了SDK给第三方开发手表应用。但是，Garmin ConnectIQ store里没有什么出色的非运动相关应用，至少类型上感觉没有 Pebble 丰富。当然 Pebble 也没有什么特别好的应用，智能手表的应用市场从来就没有活跃过。</p><p>在store上面看了受欢迎的、分数高的一些，试过后觉得值得安装的：</p><p>跑步：</p><ul><li>Single Field (data field) ：跑步信息一屏全显示，清晰易读，恰到好处；似乎没有怎么额外耗电</li></ul><p>徒步：</p><ul><li>HoneyHike (data field) ：野外徒步的信息一屏全显示，包括坡度、海拔、上升速率等；耗电约15%&#x2F;h</li><li>Chart Data Field (data field) ：显示当前活动的GPS轨迹；由于不能放大显示，无法用于走错路回溯，用处不是很大</li><li>Hike2+ (app) ：徒步专用app，有多个页面，信息比较多；运行时不能切换到其他widget（除非退出），这点限制太大；耗电约15%&#x2F;h</li></ul><p>其他：</p><ul><li>Battery Widget by disapptech (widget) ：电量监控，可以看到最近平均耗电量、预计使用时间</li><li>Battery Gauge (widget) ：比上面那个多了电量曲线；可惜目前版本在VA3上不能切换页面，等于没有用，期望以后版本能修正</li><li>Sun and Month by Garmin (widget) ：日出日落、月出月落、月相，很简单和基本的东西</li><li>Temp Pres (widget) ：我是想找温度历史曲线的，这个是气压温度二合一，界面一般</li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;img src=&quot;/blog/2019/08/25/Garmin-Vivoactive-3/vivoactive3.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;最近将 &lt;a href=&quot;/blog/2014/03/15/pebble/&quot; title=&quot;Pebble&quot;&gt;Pebble Time&lt;/a&gt; 手表换掉了，新的手表是 Garmin 的运动手表 &lt;a href=&quot;https://www.garmin.com.cn/products/sports-recreation/vivoactive3pvd/&quot;&gt;Vivoactive 3&lt;/a&gt; (VA3)。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Running" scheme="http://aleung.github.io/blog/tags/Running/"/>
    
    <category term="Gadget" scheme="http://aleung.github.io/blog/tags/Gadget/"/>
    
  </entry>
  
  <entry>
    <title>DIY人体工学分体键盘</title>
    <link href="http://aleung.github.io/blog/2019/08/04/DIY-keyboard/"/>
    <id>http://aleung.github.io/blog/2019/08/04/DIY-keyboard/</id>
    <published>2019-08-04T08:16:51.000Z</published>
    <updated>2022-08-28T15:12:31.183Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/blog/2019/08/04/DIY-keyboard/mini-thumb-dactyl.jpg"></p><p>去年底今年初先后做了两个分体的人体工学键盘。</p><span id="more"></span><p>用着微软的人体工学键盘4000好多年了（以前写过<a href="/blog/2013/01/08/keyboard-and-mouse/">一篇介绍</a>，里面也有照片），对它唯一不满的地方是太大了，占用了桌面不少空间。实际上我根本用不着小键盘，如果不要右边那一块，桌面就宽敞不少。</p><p>对市面有售的键盘又做了一番了解，找不到合适的。本来左右手分区的键盘就很罕见，符合我的期望，并且买得到和买得起根本没有。不过倒是发现有不少爱好者自己设计制作键盘并且开源的，有3D打印机打印壳体，做起来似乎不会太难。Reddit的 <a href="https://www.reddit.com/r/MechanicalKeyboards/">r&#x2F;MechanicalKeyboards</a> 是爱好者的其中一个大本营，上面各种各样的键盘让人眼花缭乱。其中，一款叫做 <a href="https://github.com/adereth/dactyl-keyboard">Dactyl</a> 的键盘，和由它派生出来的 <a href="https://github.com/tshort/dactyl-keyboard">Dactyl-Manuform</a> 键盘，有不少人制作过，资料相对齐全些，外形也比较符合我的要求：左右手按键分离、带倾斜度适应手掌自然摆放的角度、占地不多。</p><p>做键盘首先要做的功课的选择什么键轴（按键键体）和什么键帽。大部分的机械键盘爱好者玩的也就是把这些东西换来换去。我对机械键盘没有什么情结，除了在年代久远的时候用过的 IBM PC 是机械键盘，实在理解不了噼里啪啦的敲键快感在哪里。不过要自己做，能买到的键必然是机械按键了。各种各样的键轴在发烧友口中说得神乎其神，我就挑选了据说是最平庸的 Cherry 茶轴。另外，键帽也有不同高度、倾斜度和材质的讲究。我要做的键盘的键盘体自身已经带有弧度，就不需要键帽再有高度、弧度的区别，所以选择了DSA规格的无刻（上面没有印字）键帽。</p><p>受了网上一些文章的影响，第一个键盘选择了比较激进的最少键位的方案：主键盘区为3行10列，连数字键都没有，很多按键需要靠同时按拇指区的切换键来打出来。这样做的好处是手在键盘的位置是固定的，手腕就搁在手托上按什么键都不需要挪动。但是代价就是脑子不够用了：拇指需要按的切换键太多，记不住。</p><p><img src="/blog/2019/08/04/DIY-keyboard/dactyl-manuform.jpg"></p><p>用了几个星期，还是难以做到肌肉记忆不去想怎么去按键，于是打算再做了一个多些按键的。之前选择最少键位方案的另外一个原因是我的3D打印机可打印面积不够大，加一行或者加一列都会超出打印床的大小了，因此首先得解决怎么能够适应打印面积的问题。</p><p>第一个想法是将拇指区与主键盘区分离成两个部分独立打印。这就需要修改壳体的3D模型。Dactyl 这个项目比较有意思，模型是用Clojure程序生成的，因此我也就顺便（被迫）学习了 Clojure，感受还不错，终于有机会接触 lisp 风格的函数式编程了。做出来的版本是这个：<a href="https://github.com/aleung/mini-thumb-dactyl-keyboard#dactyl-manuform-thumb-part-printed-separately-wip">dactyl_split.clj</a>，打印过一个样品出来，但我没有最终完善它的所有细节，因为有了一个新的想法。</p><p>Manuform的拇指区每边有6个键。一个大拇指要按6个键太困难了，有些键的位置比较难够得着，记忆的负荷也太大，总是要想一想按哪个。按照自己用下来的情况，一个拇指可以比较容易的靠肌肉记忆按2个键，最多也就是控制3个键。把拇指区改小了，打印面积也就缩小了。</p><p>于是就做出了我的派生作品：<a href="https://github.com/aleung/mini-thumb-dactyl-keyboard#mini-thumb-dactyl">Mini Thumb Dactyl</a> —— 缩小了拇指区的 Dactyl-Manuform。也就是标题照片里面的键盘。这个即使增加了一行和一列，都能够在我的3D打印机上打出来。</p><p>制作一个键盘主要包括三部分的工作：壳体、电路、固件（firmware）。壳体用3D打印机打印，如果别人做好的现成的STL模型不满足要求，就要自己去修改。电路的核心是 <a href="https://www.wikiwand.com/zh-hk/Arduino">Arduino</a> 兼容的电路板，因为左右手键盘是独立的，两边各一片。Firmware基于开源的键盘固件 <a href="https://qmk.fm/">QMK</a>，不需要自己编码，但是它的配置与按键电路连接相关（不同的按键数量会不同），按键映射也需要自己去配置。</p><p>开源作品是不同开发者在前人基础上不断改进的过程，但也带来了很多版本分支。网上的资料比较零碎，没有从头到尾的系统有条理的文档，而且不同资料有些描述各不一样，分别适应于某个特定的分支。寻找资料、整理信息、理清关系是比较麻烦的事情。因此我把制作的具体过程中的所有关键信息整理写成了一份 <a href="https://github.com/aleung/mini-thumb-dactyl-keyboard/blob/master/guide/index.md">How to Make a Dactyl Manuform Keyboard</a> ，以此为主线，相关的知识点可以再去阅读、搜索。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;img src=&quot;/blog/2019/08/04/DIY-keyboard/mini-thumb-dactyl.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;去年底今年初先后做了两个分体的人体工学键盘。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Gadget" scheme="http://aleung.github.io/blog/tags/Gadget/"/>
    
    <category term="Maker" scheme="http://aleung.github.io/blog/tags/Maker/"/>
    
  </entry>
  
  <entry>
    <title>燃气热水器恒温控制</title>
    <link href="http://aleung.github.io/blog/2019/02/25/thermostatic-water-heater/"/>
    <id>http://aleung.github.io/blog/2019/02/25/thermostatic-water-heater/</id>
    <published>2019-02-25T15:28:32.000Z</published>
    <updated>2022-08-28T15:12:31.183Z</updated>
    
    <content type="html"><![CDATA[<p>为了选购热水器，做了好多功课。不像软件开发和电脑硬件领域，网上通过搜索引擎很难找到相关的专业一些的技术资料，大量的都是营销的文章，里面的技术相关的信息很混乱甚至是错误的。在看了各种广告、资料后，自己摸索总结了一些关于燃气热水器的恒温控制的原理。</p><span id="more"></span><h2 id="最基本的燃气热水器工作原理"><a href="#最基本的燃气热水器工作原理" class="headerlink" title="最基本的燃气热水器工作原理"></a>最基本的燃气热水器工作原理</h2><p>下图是最基本的燃气热水器的原理图，没有自动恒温功能，也就是老旧的那些机械旋钮控制的热水器。</p><img src="/blog/attachments/2019/2/heater-1.png" class=""><p>两个阀门分别在水回路和燃气回路中，分别控制水流量和火力。可是在好多热水器中，水回路阀门的旋钮是标记为“水温调节”的，根本就是乱写。我以前就总是搞不清楚这两个旋钮怎么用——既然都有水温调节了还要控制火力干什么。</p><img src="/blog/attachments/2019/2/old-heater-control.jpg" class=""><p>如果希望出水水温高一些，调节燃气阀增加供气量让火力猛一些。但是，如果进水水温太低，即使火力全开了，都到不了期望出水温度，怎么办呢？那就要控制水流量，让通过的水流少一些，就可以加热到更高温度了。这个就是所谓“水温调节”旋钮的真正作用。</p><p>根据我的推理，燃气热水器的水流量-温升工作范围大致上应该是处于如下图的阴影区域中。</p><img src="/blog/attachments/2019/2/working_area.jpg" class=""><ul><li>纵轴是水流量，横轴为水的温升。</li><li>上面水平线是水路管道可以达到的最大流量。</li><li>下面水平线是水流感应的最小工作流量，少于这个流量会自动熄火保护。</li><li>左侧绿色曲线为火力最小时的工作曲线。更小火焰就不能稳定燃烧，会熄火。如果最小温升过大，在水温较高的夏天就会出现热水太热的问题。分段燃烧、微火苗之类的技术是尽量让这个曲线往左移，</li><li>右侧红色曲线是火力最大时的工作曲线，流量越大，温度升高得越少。其中，当温升为25度时对于的流量就是热水器的额定产热水能力，单位为升&#x2F;分钟。比如，产热水能力为10L的热水器，要将水温升高50度，水流量最大不可能超过5L&#x2F;min，否则温度上不去。</li></ul><h2 id="简单的恒温控制"><a href="#简单的恒温控制" class="headerlink" title="简单的恒温控制"></a>简单的恒温控制</h2><h3 id="水温恒定控制"><a href="#水温恒定控制" class="headerlink" title="水温恒定控制"></a>水温恒定控制</h3><p>热水器正常稳定燃烧的情况下，如果进水的温度不变，水流量也是固定的，出来的水温应该是稳定的。在一次洗澡的过程中，进水温度也不会有什么变化（除去刚开始时室内管道里的水温可能会不一样，过一会就稳定了），但是水量却是会变化的，原因很多，包括水压不稳定、其他地方用水影响、调节水龙头等等，水流量一变，水温就不恒定了。</p><p>需要让出水温度自动保持恒定，需要加入反馈控制回路。简单的方法就是探测出水口的温度，与设定温度相比较，据此控制火力大小。</p><img src="/blog/attachments/2019/2/heater-2.png" class=""><p>我估计大部分低端恒温热水器的恒温控制就是这样的原理。热水器上没有机械旋钮了，变成了数码的温度设定和显示。</p><h3 id="自动水量调节"><a href="#自动水量调节" class="headerlink" title="自动水量调节"></a>自动水量调节</h3><p>但是有个问题，当进水温度太低时，即使火力最大也是无法输出达到设定温度的热水的，看之前的图的红色工作曲线。老式热水器怎么办呢？可以人手把水量旋钮关小一些，水流量小了温升就大了。可是现在的是电脑控制数字设定的热水器啊，怎么还能靠人去调节水量呢？</p><p>于是稍高一个档次的热水器就在进水水路上增加一个自动水量调节装置，这是一个记忆合金控制的阀门，会跟随进水温度而自动调节开度，水温越低它就关得越小，相当于你在冬天将热水器的进水角阀关小一点的效果。</p><p><strong>自动水量调节是不能提高恒温精度的！</strong>它解决的是冬天热水不够热的问题，解决方法是根据进水温度自动限制水流量，也就是相当于将水阀关小一点。当然，后果就是夏天你用大花洒可以畅快的洗澡，冬天可能就不行了，水量会变小。但是它至少保证了温度是足够的。</p><p>还有些热水器有“变升”功能，通过按钮选择不同的升数，说白了也就是控制进水阀门而已，选择小一点的升数就是将阀门关小一些。看看商家宣传的春秋笔法：夏天大水量的时候说舒爽，冬天水量上不去，他就不提舒爽却说节水了。</p><p>记忆合金自动水量调节虽然能够根据进水温度调节水流量，但是它不知道用户设定的期望出水温度，也就是不知道温差。例如15度水温，水量全开加热到35度是没问题的，要加热到50度就不行了。那么这个自动水量调节在进水温度15度时应不应该限制水量呢？限制多少合适？出厂时只能根据某个出水温度来设定流量，假设是40度，那么当你把热水器设置到50度，它温度还是上不去；而设置到35度时，本来水流量可以大一些的，它却给限制了。</p><h2 id="进阶的恒温控制"><a href="#进阶的恒温控制" class="headerlink" title="进阶的恒温控制"></a>进阶的恒温控制</h2><h3 id="水量伺服"><a href="#水量伺服" class="headerlink" title="水量伺服"></a>水量伺服</h3><p>水量伺服功能在比较高档的热水器才有装备，它是一个由伺服电机驱动的进水阀门，通过控制器能够比较精准的控制开合程度。</p><img src="/blog/attachments/2019/2/heater-3.png" class=""><p>水量伺服的主要用途还是类似于自动水量调节，在进水温度与期望水温温差过大时降低流量。因为它是受控的，比起不能控制的自动水量调节有更强的功能：</p><ul><li>根据进水温度传感器能够得知与期望水温的温差，根据温差控制合理的水流量，保证设置到什么温度都能产出符合设定温度的热水。</li><li>稳定水流量：当水压发生变化，例如其他地方开水关水，会影响到热水器水流量。热水器控制器通过流量传感器监控到流量的瞬间变化，可以控制水量伺服阀门动作，去抑制流量的变化。</li><li>前面说到保持恒温主要是靠燃气比例阀调节火力，但是火力的调节到水温变化是有一些时间滞后的，因此温度会出现波动。有了水量伺服，控制器可以同时调节火力和微调水流量（也称为“水气双调”），达到更迅速的出水温度调节效果。</li><li>开机快速出水：刚点火时，水管、热交换器都是冷的，需要加热一段时间后才能逐渐到达设定温度。有了水量伺服，可以在刚开机时限制比较小的水流量，让它更快的升温；到达设定温度后，再放大回正常的水流量。</li></ul><p>严格来说，带有水量伺服的才能叫水气双调，但是现在很多宣传将记忆合金自动水量调节也称为水气双调，其实那个水量根本不能被控制。</p><h3 id="更多的传感器和控制手段"><a href="#更多的传感器和控制手段" class="headerlink" title="更多的传感器和控制手段"></a>更多的传感器和控制手段</h3><p>对于恒温控制，最重要（必备）的是出水温度传感器。高级热水器会增加更多的传感器用于恒温控制，例如进水温度传感器、水流量传感器。更多的控制部件，除了最基本的燃气比例阀，还有上面提到的水量伺服。我不确定风机转速调节是否会影响温度。反正就是从两个方面控制：更精确的控制燃烧，更灵敏的控制水流量。</p><h3 id="算法"><a href="#算法" class="headerlink" title="算法"></a>算法</h3><p>虽然说传感器越多，控制手段越多，就提供了更精确控制的可能性，但是实际效果如何，还得看控制器的算法。现在的恒温热水器控制器都是微处理器运行软件，应该都是基于 <a href="https://www.wikiwand.com/zh-hant/PID%E6%8E%A7%E5%88%B6%E5%99%A8">PID 控制</a>算法，但是参数细节和各种优化手段学问就大了。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;为了选购热水器，做了好多功课。不像软件开发和电脑硬件领域，网上通过搜索引擎很难找到相关的专业一些的技术资料，大量的都是营销的文章，里面的技术相关的信息很混乱甚至是错误的。在看了各种广告、资料后，自己摸索总结了一些关于燃气热水器的恒温控制的原理。&lt;/p&gt;</summary>
    
    
    
    
    <category term="HomeAppliance" scheme="http://aleung.github.io/blog/tags/HomeAppliance/"/>
    
  </entry>
  
  <entry>
    <title>3D modeling language</title>
    <link href="http://aleung.github.io/blog/2018/08/26/3D-modeling-language/"/>
    <id>http://aleung.github.io/blog/2018/08/26/3D-modeling-language/</id>
    <published>2018-08-26T05:28:32.000Z</published>
    <updated>2022-08-28T15:12:31.183Z</updated>
    
    <content type="html"><![CDATA[<img src="/blog/attachments/2018/8/openscad.png" class=""><p>做好3d打印机，需要制作些什么就自己建模了。大部分的CAD工具都是图形交互式的，也就是靠鼠标把模型”画“出来，但我更喜欢通过建模语言来把模型描述出来。在网上搜寻和试用了一些基于3D建模语言的CAD工具，将我了解的记录一下。</p><span id="more"></span><h1 id="CAD建模语言"><a href="#CAD建模语言" class="headerlink" title="CAD建模语言"></a>CAD建模语言</h1><h4 id="OpenSCAD"><a href="#OpenSCAD" class="headerlink" title="OpenSCAD"></a>OpenSCAD</h4><p>网站：<a href="http://www.openscad.org/">http://www.openscad.org/</a></p><p>文档：<a href="http://www.openscad.org/cheatsheet/index.html">http://www.openscad.org/cheatsheet/index.html</a></p><p>这个领域的领头项目，知名度最高，使用者最多。 </p><p>虽然不是一门通用语言，但是也有不少通用语言的特性，例如变量、函数、条件判断、循环。</p><h4 id="Relativity-SCAD"><a href="#Relativity-SCAD" class="headerlink" title="Relativity SCAD"></a>Relativity SCAD</h4><p>文档：<a href="https://github.com/davidson16807/relativity.scad/wiki">https://github.com/davidson16807/relativity.scad/wiki</a></p><p>不是独立语言，只是OpenSCAD的library，但是它提供了一套全新的API替换掉OpenSCAD原有的API的大部分功能，使用起来比OpenSCAD API简单很多。 </p><p>是一个个人项目，近两年基本上没有更新了，但看上去完成度很高，也许是没有什么需要更新了，可靠程度有待验证。 </p><p>特别之处： </p><ul><li>通过描述对象之间的相对位置，大大简化了OpenSCAD建模中繁琐的transformation </li><li>通过selector对部分对象进行操作，减少了boolean operation的繁琐组合</li></ul><p>感觉相当完美的解决了OpenSCAD使用过程中的痛点，写出来的代码简洁很多。 </p><h4 id="CraftML"><a href="#CraftML" class="headerlink" title="CraftML"></a>CraftML</h4><p>网站：<a href="https://craftml.io/">https://craftml.io/</a>， <a href="https://github.com/craftml/craftml">https://github.com/craftml/craftml</a></p><p>Markup language 风格的建模语言（类似HTML）。1.x版本已经被放弃，github repo都删除了；现在的2.0 beta是重写过的，但似乎也是个死项目。 </p><h4 id="OpenJsCAD"><a href="#OpenJsCAD" class="headerlink" title="OpenJsCAD"></a>OpenJsCAD</h4><p>网站：<a href="https://github.com/jscad/OpenJSCAD.org">https://github.com/jscad/OpenJSCAD.org</a></p><p>Online IDE: <a href="https://openjscad.org/">https://openjscad.org/</a> </p><p>使用 JavaScript 语言建模，支持类似于OpenSCAD函数风格的API，和CSG对象风格API（已经deprecated）。</p><p>优势是使用 JavaScript 有很强的表达能力和扩展能力，可用利用npm模块来扩展。</p><p>不足： </p><ul><li>API混乱，1.x的API已经deprecated，2.0 API又未完善 </li><li>文档错误多 </li><li>API使用错误时没有定位错误位置，难以debug</li></ul><h4 id="SolidPython"><a href="#SolidPython" class="headerlink" title="SolidPython"></a>SolidPython</h4><p>网站：<a href="https://github.com/SolidCode/SolidPython">https://github.com/SolidCode/SolidPython</a></p><p>Python frontend for OpenSCAD。用 python 来生成 OpenSCAD 代码，也是依托于通用语言的强大表达能力和扩展能力。</p><p>近一年开发不活跃，但项目应该依然处于有维护状态。 </p><p>特别之处： </p><ul><li>first-class negative space (holes) </li><li>直接用 + - * 运算符表示 object boolean operators</li></ul><p>不足： </p><ul><li>Python多行表达式书写不方便，需要人工调整缩进</li></ul><h4 id="ImplicitCAD"><a href="#ImplicitCAD" class="headerlink" title="ImplicitCAD"></a>ImplicitCAD</h4><p>网站：<a href="http://www.implicitcad.org/">http://www.implicitcad.org/</a>，<a href="https://github.com/colah/ImplicitCAD">https://github.com/colah/ImplicitCAD</a></p><p>Online IDE：<a href="http://www.implicitcad.org/editor">http://www.implicitcad.org/editor</a> </p><p>类似于OpenSCAD的语言，按项目文档的说法是：ExtOpenScad，with many things missing, many added, and many just working differently。 </p><p>项目未完全完善，处于beta测试可用状态。 </p><p>工具本身是用Haskell实现的。 </p><p>特别之处： </p><ul><li>Extrude支持函数参数，产生变化的挤出效果 </li><li>圆弧化object对接的边缘</li></ul><h1 id="我的选择"><a href="#我的选择" class="headerlink" title="我的选择"></a>我的选择</h1><p>最先是用 OpenSCAD 的，但是用 OpenSCAD 做稍复杂一些的模型，代码就要写得很繁琐，故此寻找有没有其他更好的方案，就有了上面一批工具的尝试。</p><p>使用了一段时间的 OpenJsCAD，因为是我熟悉的js语言，v1 API有丰富的参数让代码写起来简洁一些，一度认为是最佳的选择。但后来发现它正在进行的向 v2 API 的迁移太混乱了，用户很难预期到底应该使用哪种方式。另外，近来几个月的开发工作也停滞不前了，觉得项目当前的成熟度有点问题。</p><p>其他的几个工具也都是有很大的成熟度问题，于是只好又回到了 OpenSCAD。Relativity SCAD 是最近的发现的，只用来做过一个模型，目前的体验相当好，应该可以作为今后的主要工具。可靠性、完整度还有待在更多的使用中去验证。</p>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/blog/attachments/2018/8/openscad.png&quot; class=&quot;&quot;&gt;

&lt;p&gt;做好3d打印机，需要制作些什么就自己建模了。大部分的CAD工具都是图形交互式的，也就是靠鼠标把模型”画“出来，但我更喜欢通过建模语言来把模型描述出来。在网上搜寻和试用了一些基于3D建模语言的CAD工具，将我了解的记录一下。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Maker" scheme="http://aleung.github.io/blog/tags/Maker/"/>
    
  </entry>
  
  <entry>
    <title>软件项目的标准配置和自动检查更新</title>
    <link href="http://aleung.github.io/blog/2018/04/11/project-preset/"/>
    <id>http://aleung.github.io/blog/2018/04/11/project-preset/</id>
    <published>2018-04-11T14:17:10.000Z</published>
    <updated>2022-08-28T15:12:31.183Z</updated>
    
    <content type="html"><![CDATA[<p>在<a href="/blog/2017/09/07/docker-dev-env/">上一篇文章</a>中介绍了如何使用利用 docker 容器创建微服务开发环境，解决的是怎么保证同一个软件项目在任何地方都能够快速复制出一个相同的环境用于开发和测试。但是，在使用了微服务架构的产品线中，我们还面临另外一个挑战。</p><span id="more"></span><h2 id="挑战"><a href="#挑战" class="headerlink" title="挑战"></a>挑战</h2><p>系统由很多微服务构成，每个微服务都是一个独立的软件项目——单独的 git 代码仓库，各自的环境配置，独立的测试与发布流程。目前我们的系统中已经有了几十个这样的软件项目，而且数量还在不断增加。在我们每日的常规开发工作中，都不断的面临下面的问题：</p><ul><li>怎么快速创建出一个新的软件项目，包含所有必需的配置文件，并且符合产品线的标准开发测试流程和代码规范？</li><li>怎么保证依赖的软件／库都及时更新到合适的版本？怎么防止开发人员使用了错误的版本而导致质量隐患？</li><li>怎么防止开发人员使用了 license 不合适的第三方软件／库，导致法律风险？</li><li>对于开发团队使用的公共基础设施的更改（例如 CI 服务器的某些变更），需要修改软件项目的配置的，如何快速的在所有软件项目中完成修改？</li><li>怎么保证每个项目都应用了代码检查工具，并且及时更新到符合产品线统一定义的最新规范的配置？</li><li>需要在软件的开发测试自动化流程中引入新的步骤或工具，怎么快速的部署到所有的软件项目中？</li></ul><p>由于我们采用的开发平台和开发工具都使用了文件配置，以上的问题可以归纳为三个：</p><ul><li>首先是怎么简单快捷地创建出软件项目的一组文件；</li><li>然后是对于现有的软件项目，如何检查文件的某些内容是否符合规范（比如依赖定义中是否包含正确的版本号，CI流程定义有没有被删除掉某些步骤）；</li><li>最后是怎么去更新已有的文件内容。</li></ul><p>对于第一个问题，如何快速创建新的软件项目，业内已经有很多解决方案，通常使用称之为“脚手架” (scaffold) 工具。这些工具的本质上是基于模版创建出软件项目的一组基本文件。但是现有的脚手架工具都只能帮助新软件项目的初始化工作，并不能解决第三个问题，它们对于已经进入开发阶段的项目无能为力。而在开发实践中，根本不可能设置好项目模版就一成不变，技术在发展，流程在改进，开发流程和基础设施中也会有bug需要修复，经常会出现所有软件项目都需要做一些同样的改动的情况。</p><p>以前，遇到这样的情况，就得群发邮件，通知大家去修改。效果是可想而知的，有些项目忘记修改，有些改了但是改错了没有发现。而且大家习惯于遇到问题发现要修改的时候才找另外一个项目来抄，而不是按照邮件或文档里写的方法来改，结果抄的还是一个旧版本，或者是改漏的地方，要花大量的时间来排查问题。总之，在微服务化带来大量软件项目之后，这样的工作方式是相当低效的。</p><h2 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h2><p>厌倦了不停的发公告、催促大家做修改、帮助不同人解决重复出现的问题，得寻找一个根本解决问题的方法。也就是要做到：</p><ul><li>所有的规范要求应该通过<strong>代码（工具）</strong>来<strong>强制</strong>检查或执行，而不是靠个人自觉</li><li>所有与规范相关的代码更新都应该<strong>自动化</strong>进行，而不需要让每个项目的维护者手工去一次次做修改</li></ul><h2 id="方案"><a href="#方案" class="headerlink" title="方案"></a>方案</h2><p>经过几个月的试用与改进，一套符合我们产品线最佳实践和规范的node.js项目通用设定以及自动配置工具最终实用化并开始全面推广，称为 SES Node.js Project Preset。</p><p>Preset 主要包含两部分：项目标准配置和自动检查更新工具。</p><h3 id="项目标准配置"><a href="#项目标准配置" class="headerlink" title="项目标准配置"></a>项目标准配置</h3><p>项目标准配置涵括了 node.js 项目从编辑环境、开发流程、代码检查、测试流程、CI设置到软件发布的软件开发周期各个环节。</p><p>开发人员日常需要执行的操作只有两个：</p><ul><li><code>yarn docker</code> - 启动 docker 开发环境，过程中会自动安装依赖，自动更新到最新preset</li><li><code>yarn test</code> - 包括了代码编译、格式化、静态代码检查、执行单元测试、冗余代码检查等</li></ul><p>代码提交到 GitLab 上，CI流程会自动执行所有检查流程，除了测试不通过外，任何规范性检查不通过都不能合并回主干（体现强制性）。</p><p>项目标准配置通过一系列的脚本、工具配置文件来实现。主要包括以下这些：</p><ul><li>项目基本的文件模版：README，LICENSE，CHANGELOG。</li><li>Git环境配置：.gitignore，.gitattributes。</li><li>代码格式：编辑器风格定义文件 .editorconfig，TypeScript代码自动格式化工具 tsfmt、tslint。</li><li>TypeScript编译与代码检查：tsc配置文件tsconfig.json，代码检查工具 tslint，重复代码检查工具 jscpd。</li><li>单元测试：mocha测试工具，nyc测试覆盖率报告。</li><li>软件依赖版本：package.json 中的 dependencies、devDependencies、optionalDependencies。</li><li>Docker 开发环境配置：docker-compose.yaml。</li><li>软件打包发布：rpm spec 以及 rpm build script。</li><li>GitLab配置：Merge Request模版、Issue模版、CI流程定义文件 .gitlab-ci.yml</li><li>NPM脚本：package.json中的scripts配置，包括 build，test，ci，rpm，docker，start，setup 等等。</li></ul><h3 id="自动检查与更新工具"><a href="#自动检查与更新工具" class="headerlink" title="自动检查与更新工具"></a>自动检查与更新工具</h3><p>这样一个工具的最底层操作都是文件的修改和拷贝。我没有自己从头构建，而是基于开源自动化项目配置工具<a href="https://github.com/sapegin/mrm">mrm</a>。Mrm缺省支持一些task，例如 LICENSE 文件、.gitignore、编辑器格式、ESLint规则等等。缺省task的设置是一般开源项目的通用配置，对于我们产品不太适用，但是没关系，mrm也支持自定制task，提供了一组API，可以比较方便的修改包括 JSON、YML、ini、纯文本等各种格式的文件。我开发维护了一个 preset 库，包含一组自定义task，涵括了上面一节里的所有内容。</p><p>这个 mrm preset 最重要的是它不仅仅是根据预定义模版创建文件，它还分析现有项目的配置，检查是否需要更新。某些更新并不是单纯的覆盖，而是根据项目的具体情况来生成的，例如对于 docker-compose.yaml，根据里面定义了那些测试依赖容器，自动配置被测试应用的环境变量；分析 package.json 中的依赖定义，自动更新旧版本，如果发现有未列入白名单中的依赖，则告警或报错。</p><p>虽然项目标准配置涉及的东西很多，但是有了这个工具，配置起来只需要执行一条命令：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npx -p mrm -p @ses/mrm-preset mrm all --preset @ses/mrm-preset</span></span><br></pre></td></tr></table></figure><p>项目只需要配置一次，以后 preset 有新版本发布，在 docker 开发环境启动时就会自动安装并且更新。</p><p>当项目标准配置需要更改，仅需要更新并发布npm包<code>@ses/mrm-preset</code> ，也无需通知大家改动，任何开发者在进入项目开发环境时就会自动修改，他将修改提交到 GitLab 即可。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>规范必需强制性执行，才能保证代码质量。只要存在可以通融走捷径的地方，必然会形成技术债务累积。</p><p>要做到项目的配置能够全自动更新，环境的配置均应该以文件保存，与代码一同保存做 git 库里 （Infrastructure as code）。例如 Jenkins 的配置就没办法跟随项目代码一起变动，而 GitLab CI 配置就可以，这也是我们迁移到 GitLab 的其中一个原因。配置最好采用声明式风格，而非命令式，这样才方便自动修改。比如NPM script容易改，若项目用 gulp.js 做make工具，就很难改。（采用声明式风格，基于文件的配置，是现在很多平台和工具的趋势，例如 Docker，GitLab CI，Kubernetes，Vagrant等等）</p><p>综合上次介绍的 docker 标准环境和这里介绍的项目标准配置，利用自动化工具，可以有效提高开发效率，避免大量由于环境、配置因素出现的问题。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在&lt;a href=&quot;/blog/2017/09/07/docker-dev-env/&quot;&gt;上一篇文章&lt;/a&gt;中介绍了如何使用利用 docker 容器创建微服务开发环境，解决的是怎么保证同一个软件项目在任何地方都能够快速复制出一个相同的环境用于开发和测试。但是，在使用了微服务架构的产品线中，我们还面临另外一个挑战。&lt;/p&gt;</summary>
    
    
    
    
    <category term="SoftwareDev" scheme="http://aleung.github.io/blog/tags/SoftwareDev/"/>
    
  </entry>
  
  <entry>
    <title>Kossel Mini 3D 打印机制作记录 (3)</title>
    <link href="http://aleung.github.io/blog/2018/02/26/Kossel-Mini-3D-printer-making-3/"/>
    <id>http://aleung.github.io/blog/2018/02/26/Kossel-Mini-3D-printer-making-3/</id>
    <published>2018-02-26T14:00:36.000Z</published>
    <updated>2022-08-28T15:12:31.183Z</updated>
    
    <content type="html"><![CDATA[<p>前篇：<a href="/blog/2015/10/05/kossel-mini-3d-printer-making/">(1)</a>,<a href="/blog/2017/12/31/kossel-mini-3d-printer-making/">(2)</a></p><h1 id="继续调试"><a href="#继续调试" class="headerlink" title="继续调试"></a>继续调试</h1><p>换上了淘宝上新买的喉管、铁氟龙送料管和送料管接头，出丝就正常了。另外，才知道铁氟龙管还有透明和乳白色两种，材质有点不同的，据说乳白色的更好。</p><p>第一次打印一个小方块出来，兴奋得不得了。</p><span id="more"></span><p>虽然能打印东西出来了，但实际上调试的磨难才刚刚开始。</p><p>打印校准立方体，发现问题 X、Y 尺寸有百分之几的偏差，直角不够垂直，最重要的问题是底部刚开始的几层错位严重，每层都比下面一层错位一两毫米。</p><h2 id="出料堆积"><a href="#出料堆积" class="headerlink" title="出料堆积"></a>出料堆积</h2><p>观察发现打印路径上有很多短边和转折的时候，会在端点上有出料堆积，堆多了造成打上一层时打印头刮碰。以为是这个原因造成的错位。到百度“3d打印”贴吧上问人，别人指出了几个可能问题：</p><ul><li>堆积材料因为冷却不够</li><li>打印头漏料</li><li>错位是因为步进电机丢步</li></ul><p>一检查，果然打印头有漏料。因为加热铝块和铜喷嘴的热膨胀系数不同，常温下拧紧的喷嘴高温下变得很松动，需要加热后再拧紧。</p><p>又加了小风扇在旁边吹打印模型，加速降温。但这个不是主要原因。在切片软件中降低挤出系数减少出料量，就没有堆积现象了。</p><p>电机丢步的评论我没有重视。（那时我还不知道怎样的现象是丢步，后来才发现其实这个才是真正原因。贴吧的人气很低，问问题很多天都没什么回应，但上面有些人确实有经验。）</p><p>可是，问题依然存在。</p><h2 id="框架偏差"><a href="#框架偏差" class="headerlink" title="框架偏差"></a>框架偏差</h2><p>仔细观察打印机框架，感觉立柱有一点点倾斜。用垂线测量了一下，确实是往一个方向出现了剪切位移。</p><p>框架用很多颗螺丝拧紧，型材插进角件中，有一点裕度，螺丝一压紧，难以控制它不倾斜。感觉要调正很困难，把解决的方向放在打印床上，想把玻璃打印床的固定螺丝换成弹簧可调节的，通过调整平面去让它与立柱垂直。</p><p>本来都要买调平螺丝了，晚上尝试了一下把打印机横放，底座的螺丝拧松一半，一个个直角来调整：先把螺丝拧到半紧，但用力还能稍微掰得动的程度，用直角尺量着立柱和平台的角度慢慢调整，直到把两根铝型材调整到正交，调好一处上紧一处的螺丝，一共6组。居然最后给调准了，用垂线测量基本上没有误差。</p><p>可是，这并不是导致层错位的原因。</p><h2 id="自动调平"><a href="#自动调平" class="headerlink" title="自动调平"></a>自动调平</h2><p>打印小立方体时，打印头只在打印平面的中心附近移动。尝试打大面积的校准模型，发现打印头在某些边缘位置会刮到打印床玻璃上，觉得是调平还有问题。</p><p>手工调平效率太低了，靠抽纸的方式来判断也不够准确。本来打算能自己打零件，才打印一个自动调平开关的安装座的，现在打印机的误差太大根本不能打零件。</p><p>于是买了一个压力传感器，无需安装，需要用时套到喷嘴上就行，当喷嘴下压到打印床上，传感器上下两片触片就会闭合。事实证明，这种传感器虽然贵一点，但绝对值得：</p><ul><li>使用方便，套上去就行</li><li>与喷嘴没有高度差，没有水平偏差，不需要测量和在固件中设置位移</li><li>不需要在效应器上额外安装东西，不会增加重量</li></ul><img src="/blog/attachments/2018/2/1.jpg" class=""><p>另外也怀疑endstop的机械微动开关触发不准确，买了光电阻挡触发开关，不过后来也没有装上。</p><p>有了自动调平功能，看着打印头哒哒哒的敲打着玻璃板，十几个测量点反复测多轮，手工根本做不到，效率天上地下之差。</p><p>可是，自动调平之后，打印出来的立方体变形得更加厉害了，从底到顶都是歪的。</p><h2 id="电机失步"><a href="#电机失步" class="headerlink" title="电机失步"></a>电机失步</h2><p>有了压力传感器，尝试用 G30 指令做单次Z probe，发现连测几次结果都不一样，似乎是打印头接触玻璃板后没有立即停下来，造成错位了。修改固件，降低 Z probe 速度，慢动作之下就看出来了：打印头接触玻璃板后，水平滑动了一段距离才抬起——本来应该一探到玻璃板，开关触发，就马上抬起来的。以为是压力传感器不够敏感，试了用 M119 指令查看状态，手指头轻轻一压传感器，状态就变化了，也不是传感器问题。</p><p>而且，观察到这个滑行现象在探测点靠近B柱的时候特别明显，而在其他位置就不会发生。</p><p>百思不得其解，在百度贴吧和 <a href="https://3dprinting.stackexchange.com/q/5292/162">3dprinting.stackexchange.com</a> 上提问，都没有人回答。（贴吧在半个月后有高人指出问题，可那时我已经解决了）</p><p>后来试试用手阻挡滑块的运动，感受一下电机的力度。电机的力度蛮大的，我才明白为什么刚开始出料堆积造成打印头刮碰时，别人都没当这是原因——材料被打印头刮碰时已经被加热软化，所产生的阻力对电机来说算不上什么。而试到B柱时，电机的力度不足，手稍微用力阻挡就会打滑——原来这个就叫做电机的失步！</p><p>没有亲自尝试体会，没法完全理解别人的经验。</p><p>更换了步进电机，重新自动调平，4点测量后就已经准确校准了，后面的25点、100点测量都是一个迭代就收敛。到此为止，打印功能完全正常。</p><h1 id="完善"><a href="#完善" class="headerlink" title="完善"></a>完善</h1><p>接下来，是一些完善的工作。</p><h2 id="模型散热"><a href="#模型散热" class="headerlink" title="模型散热"></a>模型散热</h2><p>E3D v6 挤出头配了散热风扇，只是给挤出头散热的，不吹打印工件，所以我用个充电宝带个小米风扇在旁边吹。这样不是太方便，而且风扇距离太远也无法做到材料一挤出喷嘴就能立即冷却固化。看别人的设计，有些是在打印头旁边加挂一个风扇吹工件，看起来臃肿占地方，也增加重量。</p><p>在 Thingiverse 上见到一个二合一的风扇风道<a href="https://www.thingiverse.com/thing:839620">设计</a>，一个风扇分出两个风道，分别给挤出头散热和吹向喷嘴周边。</p><img src="/blog/attachments/2018/2/duct1.jpg" class=""><p>制作了一个，感觉不够理想：</p><ul><li>风扇凸出太多，打印头运动到边缘时会碰撞立柱，减少了可打印范围半径</li><li>PLA材料弹性不够，夹在挤出头上可能会不够牢固</li></ul><p>想到改进的方向：</p><ul><li>外形紧凑，尽可能少凸出于效应器投影面：减少风扇倾角，尽量垂直放置；缩短风道长度</li><li>重用原来 E3D v6 配套的塑料风扇座，那个比较牢固</li></ul><p>重新设计了一个，也发布到 Thingiverse 上了：<a href="https://www.thingiverse.com/thing:2781523">https://www.thingiverse.com/thing:2781523</a></p><img src="/blog/attachments/2018/2/duct2.jpg" class=""><h2 id="挤出机导管"><a href="#挤出机导管" class="headerlink" title="挤出机导管"></a>挤出机导管</h2><p>用的是 MK8 远程挤出机，铝合金材质很结实，回抽的效果也不差，但设计还说不上很完美：</p><ul><li>虽然进料孔、出料孔和齿轮边缘在一条直线上，但由于挤丝的一边是平的挤丝齿轮一边是带槽的堕轮，丝受压就歪向带槽的一方了，造成很难将丝插入出料孔。在打印前穿丝还可以用小螺丝刀拨着方向慢慢试，在打印过程中想接丝就没可能做到了。</li><li>换丝时即使按下杠杠松开压轮，丝线还是会摩擦挤丝齿轮。但这也不算什么问题了。</li></ul><p>看到有人用铁氟龙管替换堕轮，让整个进丝通道都在管中，我也试了一下。可是打印出来的工件很疏松，送料不足的样子，就装回堕轮了。后来再想，也许将挤压丝线的压力调大能有改善。</p><p>后来在出料孔前加了一小截铁氟龙导管，虽然还是做不到不靠手工辅助就能准确插入管中，但起码好操作一些了，打印过程中也能够接上另一段丝线。</p><img src="/blog/attachments/2018/2/extruder.jpg" class=""><h2 id="电路板外壳"><a href="#电路板外壳" class="headerlink" title="电路板外壳"></a>电路板外壳</h2><p>电路板裸露会比较脆弱，得做个外壳装起来。</p><p>一个LCD控制器的外壳，一个 RAMPS＋Arduino的外壳，搞了好几天才弄好。最大的感受是：设计不难，建模不难，最麻烦的事情是怎么把尺寸测量准确。知道为什么说 3D 打印机是工业设计的好辅助工具了，电脑做的模型还是要做个真实样品出来才能验证，根据实物再做修改调整。如果没有快速成型工具，直接出模具，很可能就废了</p><p>做的各种零件，基本上都要打印两三次甚至更多才能最终确定设计。这种FDM 3D打印的速度其实很慢，半边外壳就要打印一个多小时。节省时间的方法是对于设计不确定、可能出问题的复杂的部分，先打印一块局部出来。</p><p>各种各样的测试品一堆：</p><img src="/blog/attachments/2018/2/2.jpg" class=""><p>Kossel Mini 原来的设计里，电路板是放在框架的上方的。Delta结构打印机由于悬臂，上部空间浪费很多。于是我将LCD、Arduino和挤出机都改成挂在框架横梁下面，减少了整机高度，另外处于铝合金框架的保护之中，搬动、摆放也会安全一些。</p><img src="/blog/attachments/2018/2/3.jpg" class=""><p>RAMPS＋Arduino的外壳的设计放在 Thingiverse上：<a href="https://www.thingiverse.com/thing:2806140">https://www.thingiverse.com/thing:2806140</a> 。LCD控制器我的做得不如别人的好，就没有放上去。</p><hr><p>这个3D打印机设计全过程涉及的资料，包括安装手册、参考资料、物料清单（BOM）和淘宝购买链接、打印模型源文件、照片都存放在 <a href="https://github.com/aleung/kosselprinter">https://github.com/aleung/kosselprinter</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;前篇：&lt;a href=&quot;/blog/2015/10/05/kossel-mini-3d-printer-making/&quot;&gt;(1)&lt;/a&gt;,&lt;a href=&quot;/blog/2017/12/31/kossel-mini-3d-printer-making/&quot;&gt;(2)&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;继续调试&quot;&gt;&lt;a href=&quot;#继续调试&quot; class=&quot;headerlink&quot; title=&quot;继续调试&quot;&gt;&lt;/a&gt;继续调试&lt;/h1&gt;&lt;p&gt;换上了淘宝上新买的喉管、铁氟龙送料管和送料管接头，出丝就正常了。另外，才知道铁氟龙管还有透明和乳白色两种，材质有点不同的，据说乳白色的更好。&lt;/p&gt;
&lt;p&gt;第一次打印一个小方块出来，兴奋得不得了。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Maker" scheme="http://aleung.github.io/blog/tags/Maker/"/>
    
  </entry>
  
  <entry>
    <title>Kossel Mini 3D打印机制作记录 (2)</title>
    <link href="http://aleung.github.io/blog/2017/12/31/kossel-mini-3d-printer-making/"/>
    <id>http://aleung.github.io/blog/2017/12/31/kossel-mini-3d-printer-making/</id>
    <published>2017-12-31T06:41:46.000Z</published>
    <updated>2022-08-28T15:12:31.183Z</updated>
    
    <content type="html"><![CDATA[<img src="/blog/attachments/2017/12/kossel.jpg" class=""><p><a href="/blog/2015/10/05/kossel-mini-3d-printer-making/">上一篇</a>是2015年国庆写的，当时是滑轨质量不行需要更换。后来重新买了滑轨，装配起来，测试过电路板、电机正常，然后就停工了。一放两年，这两个星期终于重新拿出来，继续制作。</p><span id="more"></span><p>两年过去，Kossel打印机已经不是DIY热门了，搜索到的新信息很少。另一方面，淘宝上配件的价格普遍下降了不少。</p><h1 id="装配"><a href="#装配" class="headerlink" title="装配"></a>装配</h1><p>其实主要部分在2015年底已经装好了，就差滑车限位开关和挤出机没装上。装起来后就是上面照片的样子啦。</p><p>电线虽然已经理过了还是有些乱，等能打印后设计个外壳把电路板罩上。</p><h1 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h1><p>装配还好，调试才是累人的活。</p><p>升级到最新的 Merlin 固件，delta结构打印机的配置参数减少了一些，网上的调试教程普遍是两三年前的，针对旧版本固件，已经不适用了。</p><h2 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h2><p>Delta 结构打印机的坐标原点在打印床正中心，三根立柱依次编号为 alpha &#x2F; beta &#x2F; gamma，但也有称为 X, Y, Z 柱，不能跟坐标系的三个轴混淆。三根立柱和xy平面的位置关系如下图，圆形为可打印半径。需要先计算好A, B, C 三点的xy坐标，后面调试要用。</p><img src="/blog/attachments/2017/12/delta_columns.jpg" class=""><p>打印头的移动可以分解为：</p><ul><li>z轴上的运动：三个柱的滑车同时上下移动相同距离；</li><li>xy平面上的运动：三个滑车上下移动不等距离，造成斜杆倾斜角度变化而改变斜杆在平面上的投影长度；</li></ul><p>需要给出的参数就是以下几个：</p><ul><li><code>DELTA_DIAGONAC_ROD</code> - 斜杆长度</li><li><code>DELTA_RADIUS</code> - 打印头处于平面正中央时，斜杆在平面上的投影长度</li><li><code>DELTA_HEIGHT</code> - 打印头可以到达的最高点的z坐标</li><li><code>DELTA_PRINTABLE_RADIUS</code> - 可打印半径</li><li><code>DEFAULT_AXIS_STEPS_PER_UNIT</code> - 各电机（包括三个柱，和挤出机）走1mm需要的步进脉冲数量</li></ul><p>打印机怎么知道滑车的位置呢？滑车由步进电机驱动，根据<code>DEFAULT_AXIS_STEPS_PER_UNIT</code>可以推算出相对运动距离。在轨道最高点（endstop）安装有感应开关，滑车复位时上行至最高点直到感应开关触发，此处的位置对应着z轴的<code>DELTA_HEIGHT</code>值。</p><img src="/blog/attachments/2017/12/delta_radius.jpg" class=""><h2 id="参数调整"><a href="#参数调整" class="headerlink" title="参数调整"></a>参数调整</h2><h3 id="初始设定"><a href="#初始设定" class="headerlink" title="初始设定"></a>初始设定</h3><p><code>DEFAULT_AXIS_STEPS_PER_UNIT</code> 这个参数的调准是第一步。我用的步进电机基本步距1.8°，RAMPS驱动板采用1&#x2F;16微步（microstepping），也就是转一圈需要3200个脉冲。XYZ柱电机安装GT2同步轮，齿距2mm，16齿，也就是转一圈滑车移动32mm，由此算出 100 steps&#x2F;mm。对于挤出机(E)，根据挤出轮直径12mm，算出周长37.7mm，得到 84.88 steps&#x2F;mm。</p><p>尽可能精确的测量斜杆长度（球头中心点距离），填入<code>DELTA_DIAGONAC_ROD</code> 。</p><p>将三个滑车推到最高点，测量打印嘴离打印床的垂直高度，稍放大几毫米余量，填入<code>DELTA_HEIGHT</code>。</p><p><code>DELTA_RADIUS</code> 的值很难测准，可以通过用细线挂重物的方式来找出垂线。两三毫米内的误差也是可以的。</p><h3 id="调准高度和endstop偏移"><a href="#调准高度和endstop偏移" class="headerlink" title="调准高度和endstop偏移"></a>调准高度和endstop偏移</h3><p>三个立柱的 endstop 感应器安装位置会有偏差，不在一个水平面上，<code>DELTA_ENDSTOP_ADJ</code> 参数可以矫正这个误差（如果有EEPROM，也可以用 G-code <code>M666</code> 设定这些参数并用<code>M500</code>保存）。</p><p>为了调校方便，需要在printing host软件中先设定4个宏，分别让打印头移动到前面基础知识坐标平面图中的 A, B, C, O 点。宏包含以下 G-code，其中x,y为点的坐标：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">G28</span><br><span class="line">G0 X&lt;x&gt; Y&lt;y&gt; Z5 F5000</span><br></pre></td></tr></table></figure><p>执行宏后，打印头会悬停在对应点上方的几毫米处。在打印床上放一张复印纸。在printing host软件中控制打印头z轴单步下降，接近后每次下降0.1mm，抽拉复印纸根据打印头有没有摩擦到纸张来判断打印头是否已经降到打印床表面，如果纸张被打印头压死抽不动就是已经太低了。记录下最后的z坐标（打印机液晶屏显示，或者<code>M114</code>指令获取）。</p><p>根据A, B, C点测量到的值，调整 <a href="http://reprap.org/wiki/G-code#M666:_Set_delta_endstop_adjustment"><code>M666</code></a> 的X, Y, Z参数，反复迭代测量、调整，直到A, B, C点打印头接触打印床时z坐标为零。这个步骤完成后，打印机的三个立柱上滑车的z高度就是准确和同步的了。</p><h3 id="调准radius"><a href="#调准radius" class="headerlink" title="调准radius"></a>调准radius</h3><p>上面调整结束后，O点数据不为零，也就是打印头在xy平面上运动，z坐标不变的时候，实际上的运动轨迹是一个碗形，可能向上凸也可能向下凹。要纠正碗形失真，需要精确设置 <code>DELTA_RADIUS</code> 参数。用 <a href="http://reprap.org/wiki/G-code#M665:_Set_delta_configuration"><code>M665</code></a> 也可以调整这个参数 (R)。</p><p>调试过程类似，也是迭代调整参数和测量，直到 A, B, C, O 点均测得零值。</p><h3 id="调准斜杆长度"><a href="#调准斜杆长度" class="headerlink" title="调准斜杆长度"></a>调准斜杆长度</h3><p>接下来的校准需要通过打印一个立方体来进行，用游标卡尺测量打印出来的真实 x, y 边长，根据与期望长度的比例来调整 <code>DELTA_DIAGONAC_ROD</code>（或<code>M665</code>的L参数）。</p><p>由于打印头问题，这个测试还没能开始。</p><h3 id="调试数据记录"><a href="#调试数据记录" class="headerlink" title="调试数据记录"></a>调试数据记录</h3><p>下表是几次迭代校准的过程，左边的是使用的参数，A／B／C／O为测量值。</p><table><thead><tr><th>M666 X</th><th>M666 Y</th><th>M666 Z</th><th>M665 H</th><th>M665 L</th><th>M665 R</th><th>A</th><th>B</th><th>C</th><th>O</th><th>Comment</th></tr></thead><tbody><tr><td>-6</td><td>-8</td><td>-6</td><td>250</td><td>214</td><td>103</td><td>0</td><td>0.8</td><td>0.7</td><td>1.3</td><td></td></tr><tr><td>-6.2</td><td>-7.2</td><td>-5.3</td><td>250</td><td>214</td><td>103</td><td>0</td><td>0</td><td>0</td><td>0.7</td><td>高度和水平已校准</td></tr><tr><td>-5.5</td><td>-6.5</td><td>-4.6</td><td>250</td><td>214</td><td>101</td><td>0.1</td><td>0</td><td>0.1</td><td>0</td><td></td></tr><tr><td>-5.5</td><td>-6.6</td><td>-4.6</td><td>250</td><td>214</td><td>101.2</td><td>0</td><td>0</td><td>0</td><td>0</td><td>radius已校准</td></tr></tbody></table><h2 id="挤出头"><a href="#挤出头" class="headerlink" title="挤出头"></a>挤出头</h2><p>运动部件的校准还说顺利，可是真准备首次打印的时候才出问题。</p><p>进丝一段之后就挤不进去了，步进电机前进一下就往回跳。喷嘴一点料都没有挤出来。尝试回抽，拔一小段后也卡住。拆开打印头，发现打印丝在离前端几毫米的地方形成了一个环，造成不能前进也不能后退。</p><p>散热风扇正常，升高降低温度都试过，四氟管已经确认插到头，都没发解决进丝一到喉管瓶颈为就堵塞的问题。从拔出来的堵塞物看，是丝进入喉管瓶颈位置后软化膨胀变粗，刮起表面一层，堆积成环状就卡死了。</p><img src="/blog/attachments/2017/12/jam.jpg" class=""><p>这卷耗材还是两年前的，拆过封口，时间太长会对材料有影响。换了一卷没有拆封的耗材来测试，这次能过顺利进丝通过瓶颈位，喷嘴也出丝了。可是接下来加载模型打印，又不出料了。</p><p>上网查资料，发现现在的喉管有改进型的，里面带铁氟龙管内衬，或者是可以让进料铁氟龙管直通过去，一直到喷嘴处。在耗材可能软化的段都是铁氟龙管内衬，摩擦力很小而且没有接缝，就不容易出现堵塞。</p><img src="/blog/attachments/2017/12/hotend.jpg" class=""><p>元旦店家都休息了，又一次陷入等待状态……</p>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/blog/attachments/2017/12/kossel.jpg&quot; class=&quot;&quot;&gt;

&lt;p&gt;&lt;a href=&quot;/blog/2015/10/05/kossel-mini-3d-printer-making/&quot;&gt;上一篇&lt;/a&gt;是2015年国庆写的，当时是滑轨质量不行需要更换。后来重新买了滑轨，装配起来，测试过电路板、电机正常，然后就停工了。一放两年，这两个星期终于重新拿出来，继续制作。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Maker" scheme="http://aleung.github.io/blog/tags/Maker/"/>
    
  </entry>
  
  <entry>
    <title>Microservice development environment make easy with Docker</title>
    <link href="http://aleung.github.io/blog/2017/09/07/docker-dev-env/"/>
    <id>http://aleung.github.io/blog/2017/09/07/docker-dev-env/</id>
    <published>2017-09-07T05:37:23.000Z</published>
    <updated>2022-08-28T15:12:31.183Z</updated>
    
    <content type="html"><![CDATA[<p>正在负责的产品线已经全面转为基于 node.js 技术栈微服务架构，为了提高开发效率，我希望能创建一个标准的、容易创建的开发环境，以改善开发环境之间不一致、可能不完备、创建配置麻烦的现状。</p><p>对这个开发环境的要求是 lightweight，reproducible 和 portable。</p><span id="more"></span><p>开始尝试的是 <a href="https://www.vagrantup.com/">Vagrant</a>，但感觉缺点还是不少：provision 过程麻烦了些；如果要同时进行不同的开发就要运行多个VM，占用资源比较多；而且有个关键问题没找到好的解决方法：Windows 的文件系统不支持符号链接 (symbolic link)，而 npm install 时会依赖于符号链接，VM 使用 Windows host 的共享目录时在里面执行 npm install 会出错。</p><p>这时 docker 进入了视野，容器的轻量、自包含一切的特性很满足我对标准化开发环境的要求。经过几天折腾，搞出了这样一套环境，初步试用还比较满意。</p><p>这个基于 docker 的开发环境做到了这些：</p><ul><li>可以运行于任何操作系统、任意主机上，只需要安装了 docker。我们用到的环境包括每个开发者的 PC、远程的公共开发机和测试 lab 里的主机，操作系统包括 Win7、Linux 和 macOS。</li><li>在满足要求的机器上（装好 docker 和其他必须的软件），敲一个命令，在一分钟内就可以从无到有创建出一个开发环境。</li><li>开发环境中所有东西都是预定义和存储于公共仓库（包括 git，docker镜像和 npm 模块仓库）中，每个人在任何机器上创建出来的环境都会是一摸一样的。</li><li>定义出一套标准的、可重用的环境，统一各个微服务项目的开发体验。</li></ul><p>这个开发环境覆盖了一个微服务的日常开发流程，包括编码、构建、质量检查、单元测试等等。但不含括要求跨服务集成的测试，例如系统测试、端到端测试。</p><p>环境包括预先创建的用于开发、测试的多个docker镜像。其中最主要的是 node.js 环境的镜像，其中包括了基本的 Linux shell 环境和 node.js 开发运行用到的软件。其他镜像是单元测试中需要依赖到的工具。</p><img src="/blog/attachments/2017/9/images.png" class=""><p>每个微服务有自己独立的开发环境，通过 <code>docker-compose</code> 启动起来。被开发的服务在 node.js 容器中构建和测试，源代码放在host文件系统中，通过 volume与容器共享，这样开发者可以在 host 上随意使用喜欢的编辑器修改代码，也绕开容器没法运行图形界面应用的问题。根据需求，将单元测试要用到的容器 link 到 node.js 容器，构造出测试环境。</p><img src="/blog/attachments/2017/9/microservice-dev.png" class=""><p>于微服务开发，是不需要暴露任何端口出来的，因为测试过程都在容器中。对于 web 应用开发，要从容器中映射出 http 端口供 host 的浏览器访问，如果需要做 web debug，还可以将 debug 端口暴露出来。</p><p>一个 host 上可以同时进行多个项目的开发，环境完全独立，相互并不干扰。</p><img src="/blog/attachments/2017/9/isolated-env.png" class=""><p>在调试这个环境的过程中，最麻烦的是 Win7 上运行 docker。在 Win10 之前的环境中运行 docker，当某些特殊字符在控制台上显示时，有可能引发 docker 出现 “New state of ‘nil’ is invalid” 的错误。详细情况在 docker issues <a href="https://github.com/moby/moby/issues/21323">#21323</a> 中 <a href="https://github.com/moby/moby/issues/22345">#22345</a> 有描述。后来找到变通方案：安装  <a href="http://conemu.github.io/en/Downloads.html">ConEmu</a> 或者 <a href="http://cmder.net/">Cmder</a>，并且将 ConEmu 设置为缺省终端，替换掉 Windows 自带的终端，就可以避免错误的发生。</p><p>Windows 环境里还有一个限制：只有 <code>C:/Users/</code> 下面的目录才能被挂载成 docker volume，所以源代码目录必须放在 <code>C:/Users/&#123;your_id&#125;/</code> 之下.</p><p>一个开发环境需要的容器以及相关配置都定义在 <code>docker-compose.yml</code> 文件中了，以下为一个例子：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">target:</span></span><br><span class="line">    <span class="attr">hostname:</span> <span class="string">foo-service</span>    <span class="comment"># set it to your project name</span></span><br><span class="line">    <span class="attr">image:</span>  <span class="string">internal.docker.hub/dev/nodejs</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$&#123;PWD&#125;:/project</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">~/.cache/yarn:/usr/local/share/.cache/yarn</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">postgres</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">PgDatabaseAddress:</span> <span class="string">postgres</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">postgres:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">internal.docker.hub/dev/postgres</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">POSTGRES_PASSWORD:</span> <span class="string">test</span></span><br><span class="line">      <span class="attr">POSTGRES_USER:</span> <span class="string">test</span></span><br></pre></td></tr></table></figure><p>另外，对于 node.js 项目，我们也定义了统一的 <code>package.json</code> 模版，里面包含了开发过程各种操作的 npm scripts，包括 TypeScript 编译、代码格式化、tslint检查、单元测试、文档生成等等。</p><p>要在一台机器上开始某个项目的开发，要做的事情只需要：</p><ol><li>将源代码 git clone 的本地目录，里面包括了定义docker环境的  <code>docker-compose.yml</code> 和定义 node.js 项目的 <code>package.json</code>。</li><li>运行 <code>docker-compose up --rm target</code>，启动 docker 环境。启动时会自动安装所有 npm 依赖。</li><li>这时终端会进入到容器的 bash 中，在这儿就可以像在本地环境一样进行开发操作了，例如：<code>yarn build</code>, <code>yarn test</code>。</li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;正在负责的产品线已经全面转为基于 node.js 技术栈微服务架构，为了提高开发效率，我希望能创建一个标准的、容易创建的开发环境，以改善开发环境之间不一致、可能不完备、创建配置麻烦的现状。&lt;/p&gt;
&lt;p&gt;对这个开发环境的要求是 lightweight，reproducible 和 portable。&lt;/p&gt;</summary>
    
    
    
    
    <category term="SoftwareDev" scheme="http://aleung.github.io/blog/tags/SoftwareDev/"/>
    
  </entry>
  
  <entry>
    <title>Pact - 微服务的契约测试</title>
    <link href="http://aleung.github.io/blog/2017/06/21/pact/"/>
    <id>http://aleung.github.io/blog/2017/06/21/pact/</id>
    <published>2017-06-21T06:52:23.000Z</published>
    <updated>2022-08-28T15:12:31.183Z</updated>
    
    <content type="html"><![CDATA[<p>微服务架构的系统中，存在着大量的服务，每个服务开放出接口（作为provider），接口可以被很多其他服务调用（consume）。接口API是服务提供者和服务消费者之间的契约，理论上，只要测试了双方的实现都完全符合API，就可以保证它们能够正常的集成到一起；但在现实场景中，对API难以给出非常精确的定义，覆盖测试完整API的工作量也会非常巨大，再加上随着API的演进，要验证兼容性更是很麻烦。</p><p>消费者驱动契约（<a href="http://martinfowler.com/articles/consumerDrivenContracts.html">Consumer Driven Contract</a>）测试试图解决这个问题。它从接口的消费者出发，记录下消费者使用接口的各种场景，以此作为契约，验证接口的提供者是否符合。<a href="https://docs.pact.io/">Pact</a> 就是支持针对 HTTP API 的消费者驱动契约测试的工具。</p><span id="more"></span><p>传统上会将测试做以下分类：</p><ul><li>单元测试：测试单个service</li><li>集成测试：测试由多个services组成的系统</li><li>端到端测试：测试从用户到各个外部系统的整个场景</li></ul><p>契约测试是对单元测试的增强，针对服务接口provider测试，覆盖了一部分本来需要集成测试才能测试到的场景。</p><p>假设我们有一个模块 P 提供了某个API，这个API被其他三个模块 C-1、C-2、C-3 使用了。集成测试需要搭建起一个环境，把这几个模块都部署起来，作为一个整体来测试，验证它们之间的 API 调用是否正确。</p><img src="/blog/attachments/2017/6/integration-test.png" class=""><p>要对这些模块独立进行单元测试，就需要mock API 的 provider 或者 consumer。每个绿色框框都是独立的测试环境，相互没有依赖。但问题是，对 P 的测试，要覆盖 API 的各种调用场景，需要构造出很多测试用例，分别不同的设置 mock 的请求和期望响应。这样不但很繁琐，而且还很难确保已经覆盖了各种不同 consumer 的真正实用场景。</p><img src="/blog/attachments/2017/6/unit-test.png" class=""><p>引入了pact之后，对于 consumer 的测试区别不大，依然要写各种测试用例，给出 API 调用的期望请求和对于响应，只不过是换成用 pact 工具来做mock。在测试执行过后，pact 会将 API 请求以及响应（测试用例中模拟的）记录到 pact file（JSON格式）中，作为消费者端的契约。</p><p>对于 provider 的测试就不同了，不需要再构造测试用例来测试 API，而是将 pact files 作为输入，pact 会跟进消费者契约生成 API 请求，并且检查 provider 的响应是否符合契约。</p><img src="/blog/attachments/2017/6/test-with-pact.png" class=""><p>使用了 pact 之后，依然是每个服务独立的进行单元测试，但是可以模拟出真实集成场景。</p><p>Pact 还提供了 pact broker 去管理 pact files，以便方便的发布、获取契约，了解服务之间的依赖关系。对于由非常多服务组成的复杂系统，会有很大帮助。</p><p>但是，使用消费者驱动契约测试是有限制的，只适用于测试系统内部服务：</p><ul><li>消费者、提供者都使用同样的契约测试工具进行测试</li><li>服务的消费者必须是可全部罗列的，所有服务消费者的测试用例需要完备，覆盖它调用 API 的所有场景，否则服务提供者的测试覆盖就会不足</li></ul><p>Read more:</p><ul><li><a href="http://dius.com.au/2016/02/03/microservices-pact/">Pact 101</a></li><li><a href="http://zhouqing86.github.io/2017/04/08/js-test11-pact-test/">JS测试之Pact测试</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;微服务架构的系统中，存在着大量的服务，每个服务开放出接口（作为provider），接口可以被很多其他服务调用（consume）。接口API是服务提供者和服务消费者之间的契约，理论上，只要测试了双方的实现都完全符合API，就可以保证它们能够正常的集成到一起；但在现实场景中，对API难以给出非常精确的定义，覆盖测试完整API的工作量也会非常巨大，再加上随着API的演进，要验证兼容性更是很麻烦。&lt;/p&gt;
&lt;p&gt;消费者驱动契约（&lt;a href=&quot;http://martinfowler.com/articles/consumerDrivenContracts.html&quot;&gt;Consumer Driven Contract&lt;/a&gt;）测试试图解决这个问题。它从接口的消费者出发，记录下消费者使用接口的各种场景，以此作为契约，验证接口的提供者是否符合。&lt;a href=&quot;https://docs.pact.io/&quot;&gt;Pact&lt;/a&gt; 就是支持针对 HTTP API 的消费者驱动契约测试的工具。&lt;/p&gt;</summary>
    
    
    
    
    <category term="SoftwareDev" scheme="http://aleung.github.io/blog/tags/SoftwareDev/"/>
    
  </entry>
  
  <entry>
    <title>使用OsmAndMapCreator及时更新OsmAnd地图</title>
    <link href="http://aleung.github.io/blog/2017/06/17/OsmAndMapCreator/"/>
    <id>http://aleung.github.io/blog/2017/06/17/OsmAndMapCreator/</id>
    <published>2017-06-17T07:02:32.000Z</published>
    <updated>2022-08-28T15:12:31.183Z</updated>
    
    <content type="html"><![CDATA[<img src="/blog/attachments/2017/6/osmand.jpg" class=""><p>使用 <a href="https://www.openstreetmap.org/">OpenStreetMap</a> （简称为OSM）地图数据的手机应用有好些，<a href="http://osmand.net/">OsmAnd</a> 是其中比较优秀的一个。OsmAnd的地图数据每月更新一次，延后了半个月到一个多月。作为一个OSM mapper，期望能够马上看到和用到最近做出的修改，等一个月实在太久了。</p><span id="more"></span><p>OSM原始地图数据在 <a href="http://wiki.openstreetmap.org/wiki/Planet.osm">planet.osm</a> 的网站可以下载到，但数据文件是全球的，非常大。从 <a href="http://download.geofabrik.de/">GeoFabrik</a> 可以下载按国家分割的数据文件。中国区的PBF格式数据是330MB。但每天重新下载一个数据包还是太大了，其实更新的数据不是很多。网站上还提供每天（planet.osm还有每小时）的变更数据包（osc文件），用 <code>osmupdate</code> 工具就能自动下载变更并且生成新的完整数据文件。 </p><p>有了最新的 osm.pbf 数据文件，就可以使用 <code>OsmAndMapCreator</code> 生成 OsmAnd 可用的 OBF 格式离线地图文件了。生成的过程非常耗内存和CPU，全中国的地图在一台 Core i3-4370 CPU 的电脑上用了90分钟，需要分配 10GB 内存才够用（否则会出现 OutOfMemory 错误）。生成出来的 OBF 文件也有845MB之巨。为了缩短时间，我设置了 <code>osmupdate</code> 的<code>-b</code>参数，过滤出广东中部区域的数据，这样就快很多，几分钟搞定。</p><p>处理的流程如下图。实际上安装好软件，修改好配置后，每次更新就执行一个脚本，过程全自动。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Previous full map data (osm.pbf) \</span><br><span class="line">                              [osmupdate] --&gt; Latest full map data (osm.pbf)</span><br><span class="line">            Recent changes (osc) /                      |</span><br><span class="line">                                                        v</span><br><span class="line">[OsmAnd] &lt;------ Latest offline map (obf) &lt;------- [OsmAndMapCreator]</span><br></pre></td></tr></table></figure><p>步骤和脚本放在 gist：<a href="https://gist.github.com/aleung/42776348d023b689e8c337b82a3c7cd5">https://gist.github.com/aleung/42776348d023b689e8c337b82a3c7cd5</a> ，适用于 MacOS 和 Linix，需要自行修改文件中的目录路径。工具在Win平台也可以用，但脚本就要重新写了。</p>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/blog/attachments/2017/6/osmand.jpg&quot; class=&quot;&quot;&gt;

&lt;p&gt;使用 &lt;a href=&quot;https://www.openstreetmap.org/&quot;&gt;OpenStreetMap&lt;/a&gt; （简称为OSM）地图数据的手机应用有好些，&lt;a href=&quot;http://osmand.net/&quot;&gt;OsmAnd&lt;/a&gt; 是其中比较优秀的一个。OsmAnd的地图数据每月更新一次，延后了半个月到一个多月。作为一个OSM mapper，期望能够马上看到和用到最近做出的修改，等一个月实在太久了。&lt;/p&gt;</summary>
    
    
    
    
    <category term="GPS_GIS" scheme="http://aleung.github.io/blog/tags/GPS-GIS/"/>
    
    <category term="Map" scheme="http://aleung.github.io/blog/tags/Map/"/>
    
  </entry>
  
  <entry>
    <title>在非GPL应用中使用OpenJDK的法律问题</title>
    <link href="http://aleung.github.io/blog/2017/01/06/Use-OpenJDK-in-proprietary-software/"/>
    <id>http://aleung.github.io/blog/2017/01/06/Use-OpenJDK-in-proprietary-software/</id>
    <published>2017-01-06T14:56:07.000Z</published>
    <updated>2022-08-28T15:12:31.183Z</updated>
    
    <content type="html"><![CDATA[<p>大家都知道 Oracle JDK 在商业应用场合是需要购买 license 的，往往会选择 OpenJDK 来规避。但是，OpenJDK 的 license 是 GPL，GPL 是一种 “传染性” 的协议，那么 OpenJDK 是否可以在非 GPL 应用中使用呢？</p><span id="more"></span><p>在 <a href="http://stackoverflow.com/">StackOverflow</a>、<a href="http://opensource.stackexchange.com/">Open Source Stack Exchange</a> 中搜索到一些相关的讨论，大部分答案是认为可以使用，应用无需因此用 GPL 发布，但也有人提出质疑。</p><p>在 OpenJDK 网站中的 <a href="http://openjdk.java.net/faq/">FAQ</a>是这么写的：</p><blockquote><h3 id="What-open-source-license-is-OpenJDK-published-under"><a href="#What-open-source-license-is-OpenJDK-published-under" class="headerlink" title="What open-source license is OpenJDK published under?"></a>What open-source license is OpenJDK published under?</h3><p>GPL v2 for almost all of the virtual machine, and GPL v2 + the Classpath exception for the class libraries and those parts of the virtual machine that expose public APIs.</p><h3 id="How-do-I-know-which-license-applies-to-a-given-source-code-file-in-OpenJDK"><a href="#How-do-I-know-which-license-applies-to-a-given-source-code-file-in-OpenJDK" class="headerlink" title="How do I know which license applies to a given source code file in OpenJDK?"></a>How do I know which license applies to a given source code file in OpenJDK?</h3><p>Each source code file is individually licensed - look for the copyright header with the license information.</p></blockquote><p>比较坑爹的是整个 OpenJDK 居然不是统一使用一种授权协议，每个源文件有自己独立的授权——要明确你使用的法律责任，得一个一个源文件看去！不过，按照 FAQ 里面的说法（如果它是准确的），类库和 JVM 的公共 API 都是用 GPL v2 ＋ Classpath exception (GPL v2 + CE) 发布的。</p><p>这个 classpath exception 是什么回事？GPL v2 要求：基于 GPL 软件派生出来的作品也需要继承使用 GPL 协议发布。一个程序动态链接或者静态链接了一个库，都视为此库的派生作品。按照这个定义，在 Java 程序里使用了（在classpath中包含了）某个类库，就是此类库的派生作品。然后，OpenJDK 的某些源代码的授权声明中，在 GPLv2 协议文本之后附加了一段 <a href="http://openjdk.java.net/legal/gplv2+ce.html">classpath exception</a>，对于链接这种情况给予了豁免，可以自由选择授权协议。</p><p>我的理解，这是为了让 OpenJDK 可被商业应用（非 GPL 授权应用）使用。JVM (HotSpot) 的代码大部分都是用 GPL （没有 classpath exception）授权，但因为 Java 应用只是在 VM 上运行，并没有与 VM 链接，所以不受 GPL 的限制。</p><p>但是，没有那么简单。咨询公司开源部门的专业人士，他让我谨慎的评估一下。因为 OpenJDK 作为一个开源项目，并没有严格的控制提交代码采用的授权协议，很可能某个代码提交增加了一个新的源文件，其授权是不带 CE 的，或者将已有文件的授权的 CE 部分删除了，应用使用了这个源文件编译出来的库就中招了。</p><p>我下载了 OpenJDK7 的源代码，用脚本扫描了所有 <code>*.java, *.c*, *.h*</code> 文件，找出带有 GPL 但又不包含 Classpath exception 的文件。除去 HotSpot 部分和 example 代码，并不多：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">./share/lib/security/BlacklistedCertsConverter.java</span><br><span class="line">./aix/porting/porting_aix.*</span><br><span class="line">./share/native/sun/security/ec/impl/*</span><br></pre></td></tr></table></figure><p>第一个是个工具（带有main函数），不是库的一部分；第二个是 AIX 系统相关的；第三个是属于 <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html#SunEC">SunEC provider</a>，实现椭圆曲线加密算法（ECC）的。</p><p>需要我们注意的只有第三个，对应着文件 <code>jre/lib/ext/sunec.jar</code>。也就是，Java应用确保不使用这个jar，就可以用 OpenJDK 而不需要用 GPL 发布。一旦使用了 <code>sunec.jar</code>，整个 Java 应用都必须用 GPLv2（或其兼容协议）发布，开放源代码。</p><p>但我上面的分析仅仅是基于当前版本的 OpenJDK7，OpenJDK 每次发布代码都会变化，故此每次要安装升级时都必须重新检查它的所有源代码的授权协议。对于商业软件来说，保险起见还是乖乖给 Oracle 付费使用他们家的 JDK 吧。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;大家都知道 Oracle JDK 在商业应用场合是需要购买 license 的，往往会选择 OpenJDK 来规避。但是，OpenJDK 的 license 是 GPL，GPL 是一种 “传染性” 的协议，那么 OpenJDK 是否可以在非 GPL 应用中使用呢？&lt;/p&gt;</summary>
    
    
    
    
    <category term="SoftwareDev" scheme="http://aleung.github.io/blog/tags/SoftwareDev/"/>
    
    <category term="Java" scheme="http://aleung.github.io/blog/tags/Java/"/>
    
    <category term="OpenSource" scheme="http://aleung.github.io/blog/tags/OpenSource/"/>
    
  </entry>
  
  <entry>
    <title>Consul 集群不稳定的分析</title>
    <link href="http://aleung.github.io/blog/2016/11/05/Consul-unstable/"/>
    <id>http://aleung.github.io/blog/2016/11/05/Consul-unstable/</id>
    <published>2016-11-05T03:22:20.000Z</published>
    <updated>2022-08-28T15:12:31.183Z</updated>
    
    <content type="html"><![CDATA[<p>一个 Consul 集群由3个 Consul server 和近百个 Consul client 组成。观察发现集群状态不稳定，频繁出现以下现象：</p><ol><li>节点退出集群，又重新加入集群；</li><li>重新选举 leader，有时候原来的 leader 会重新当选。</li></ol><span id="more"></span><p>对于第一个问题，分析发现原因是我们错误使用了 fail fast 策略：我们平台中会有个supervisor daemon监控着进程的状态，当进程 health check 失败了，就会重启进程并且发送告警。对于 Consul agent 进程，health check 调用 Consul API，API不能正常返回就认为进程状态不健康。这样做本来的用意是及时发现集群配置的错误，另外也是服务依赖管理的需要，依赖 Consul 的其它服务会等到 Consul agent 进入正常状态后才启动。但是，由于第二个问题的存在，在 leader 重新选举过程中 API 不能正常响应，Consul agent 就被当作不健康而被重启，出现了 Consul 节点退出重新加入集群的现象。本来，群集是能自动恢复到正常工作状态的，重启进程反而造成更大的负面影响。</p><p>对于第二个问题，直接原因是某个 Consul server 所在的VM上有个异常的进程将CPU几乎全耗尽了，Consul 不能及时的处理集群间的通信，响应超时，产生了临时性的脑裂。但开始我们一直不能理解，单个 server 的 partitioning 为什么会影响到整个集群呢？剩余的 2 个 server 应该还是能够组成集群并保持稳定的 leader 才对。后来才发现，比起全部丢失，消息的部分丢失所引起的现象会诡异得多。</p><p>这是当前 leader cm121 的日志：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">50:32 [WARN] raft: Rejecting vote from 172.17.52.84:8300 since we have a leader: 172.17.52.121:8300</span><br><span class="line">50:32 [ERR] raft: peer 172.17.52.84:8300 has newer term, stopping replication</span><br><span class="line">50:32 [INFO] raft: aborting pipeline replication to peer 172.17.52.84:8300</span><br><span class="line">50:32 [INFO] raft: aborting pipeline replication to peer 172.17.52.85:8300</span><br><span class="line">50:32 [INFO] consul: cluster leadership lost</span><br><span class="line">50:32 [INFO] raft: Node at 172.17.52.121:8300 [Follower] entering Follower state</span><br><span class="line">50:33 [WARN] raft: Heartbeat timeout reached, starting election</span><br><span class="line">50:33 [INFO] raft: Node at 172.17.52.121:8300 [Candidate] entering Candidate state</span><br><span class="line">50:34 [INFO] raft: Election won. Tally: 2</span><br><span class="line">50:34 [INFO] raft: Node at 172.17.52.121:8300 [Leader] entering Leader state</span><br><span class="line">50:34 [INFO] consul: cluster leadership acquired</span><br><span class="line">50:34 [INFO] consul: New leader elected: cm121</span><br></pre></td></tr></table></figure><p>在这个例子中，CPU耗尽的 server 是 cm84，当前 leader 为 cm121。cm121 会定期发出 AppendEntries RPC，但 cm84 因为没有CPU资源没有处理到，等到 heartbeat timeout，cm84 错误认为当前没有 leader 了，就向所有其它 server 发出 RequestVote RPC（带有更大的 term 值），自荐为候选人，发起一轮新的投票。但是这个投票请求被其它 server 都拒绝了。</p><img src="/blog/attachments/2016/11/raft_state.png" class=""><p>根据状态图可知，当 leader 收到其它 server 发来的消息有更大的 team 值时，就需要交出 leader 位置，转为 follower。因此被 cm84 一捣乱，集群里面就没有 leader 了，导致接下来的超时重新选举。在新一轮选举中，cm121得到两票，重新当选为 leader。</p><p>将消息的时间线画出来，其中 S1 为当前 leader (cm121)，S3 为会间歇性丢掉消息的 server (cm84)，可见 S3 如何导致重新选举 leader。</p><img src="/blog/attachments/2016/11/timeline.jpg" class=""><p>对于 Consul 中 raft 实现的行为：reject vote since we have a leader，似乎跟 raft 论文描述的有些出入。在 Raft <a href="https://raft.github.io/raft.pdf">论文</a>中 RequestVote RPC receiver implementation 部分，没有提到已有 leader 时需要 reject 新的 vote。不过，即使没有这个检查，cm84 大部分情况下还是不能满足当选条件，因为它错失了 leader 发来的 AppendEntries RPC，它的 log 就不一定是 up-to-date 的（除非错失的 AppendEntries 没有 log entries，只是heartbeat）。cm84 不能当选，cm121被迫退位，群集还是会进入无 leader 状态。</p><blockquote><p><strong>RequestVote RPC</strong></p><p>Invoked by candidates to gather votes (§5.2).</p><p>Arguments:</p><ul><li>term : candidate’s term</li><li>candidateId : candidate requesting vote</li><li>lastLogIndex : index of candidate’s last log entry (§5.4)</li><li>lastLogTerm : term of candidate’s last log entry (§5.4)</li></ul><p>Results:</p><ul><li>term : currentTerm, for candidate to update itself</li><li>voteGranted : true means candidate received vote</li></ul><p>Receiver implementation:</p><ol><li>Reply false if term &lt; currentTerm (§5.1)</li><li>If votedFor is null or candidateId, and candidate’s log is at<br>least as up-to-date as receiver’s log, grant vote (§5.2, §5.4)</li></ol></blockquote><p>下一步要做的是避免 Consul 进程得不到足够的CPU资源，计划尝试修改 Consul 进程的 nice 来提高调度优先级，或者配置 CPU affinity 绑定一个CPU核给 Consul。</p><p>另外，在分析过程中也进一步加深了对 Consul 的理解。</p><p>Consul agent 分为两种：server 和 client。实际上，这里是两个不同层次的群集（当涉及到多个 datacenter 时，还多一个 WAN layer，这里就不讨论了）。</p><img src="/blog/attachments/2016/11/consul_cluster.jpg" class=""><p>所有 server 通过 Raft 协议组成一个集群，是一个 CP 系统。Consul 的 catelog 和 KV store 服务都是在这个 raft 集群中提供的，虽然 API 在 client 上也提供，但 client 只是转发给 server 来处理。集群中只会选出最多一个 leader，能容忍 partition 的发生（脑裂）。当存在 pratitioning 时，leader 在有多数 server 的分区里，只有少数 server 的分区选不出 leader。数据写入都由 leader 处理，保证了强一致性。 数据读取根据一致性要求（consistency mode），可以由 leader 或者 follower 处理。在没有选出 leader 的时候，Consul 的大部分 API 会不可用（consistency mode 为 stale 的读请求应该可以处理，需要测试确认一下）。</p><p>所有 consul agent，包括 server 和 client，组成一个更大的集群，就是平常所说的 consul cluster。这个集群是通过 Serf 协议（一种 gossip 协议）组成的，是一个AP系统，用于 consul 节点的发现和管理。AP系统只能保证最终一致性，因此 consul 节点的加入或者离开，是不能立刻被整个群集所见到的。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;一个 Consul 集群由3个 Consul server 和近百个 Consul client 组成。观察发现集群状态不稳定，频繁出现以下现象：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;节点退出集群，又重新加入集群；&lt;/li&gt;
&lt;li&gt;重新选举 leader，有时候原来的 leader 会重新当选。&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    
    <category term="SoftwareDev" scheme="http://aleung.github.io/blog/tags/SoftwareDev/"/>
    
  </entry>
  
</feed>
