<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title>Lexin Gong</title>
  <link href="https://gonglexin.com/atom.xml" rel="self"/>
  <link href="https://gonglexin.com/"/>
  <updated>2026-05-21T01:51:02+00:00</updated>
  <id>https://gonglexin.com/</id>
  <author>
    <name>gonglexin</name>
    <email>gonglexin@gmail.com</email>
  </author>

  
  <entry>
    <title>自建房网络与智能家居规划方案</title>
    <link href="https://gonglexin.com/posts/home-network-smart-home-plan"/>
    <updated>2026-05-18T00:00:00+00:00</updated>
    <id>https://gonglexin.com/posts/home-network-smart-home-plan</id>
    <content type="html">&lt;p&gt;房子从动工到现在两年多了，第一年土建，第二年装修，现在硬装快收尾了。&lt;/p&gt;

&lt;p&gt;面积不算小，单靠一个家用路由器肯定扛不住。而且现在的 IoT 设备越来越多，摄像头又涉及到隐私，随便塞一个网段里总觉得不太放心。所以这次干脆从 VLAN 开始规划，把可信设备、智能家居、安防监控和访客网络隔离开来，顺便把每个房间的智能开关和窗帘电机也一并安排上。&lt;/p&gt;

&lt;p&gt;下面是整个方案，从网络拓扑到设备清单到费用，能想到的都写上了。另外还有一个 AI 生成的&lt;a href=&quot;/html/network-topology.html&quot;&gt;可视化版本&lt;/a&gt;，看着更直观。&lt;/p&gt;

&lt;h2 id=&quot;vlan-划分&quot;&gt;VLAN 划分&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;VLAN&lt;/th&gt;
      &lt;th&gt;名称&lt;/th&gt;
      &lt;th&gt;子网&lt;/th&gt;
      &lt;th&gt;用途&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;V10&lt;/td&gt;
      &lt;td&gt;管理网络&lt;/td&gt;
      &lt;td&gt;10.10.10.0/24&lt;/td&gt;
      &lt;td&gt;交换机、AP、网关管理界面&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;V20&lt;/td&gt;
      &lt;td&gt;可信网络&lt;/td&gt;
      &lt;td&gt;10.20.20.0/24&lt;/td&gt;
      &lt;td&gt;电脑、电视、手机、房间网口、旁路由、&lt;strong&gt;中枢网关&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;V30&lt;/td&gt;
      &lt;td&gt;智能家居&lt;/td&gt;
      &lt;td&gt;10.30.30.0/24&lt;/td&gt;
      &lt;td&gt;米家 WiFi 设备（音箱、扫地机等 IoT）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;V40&lt;/td&gt;
      &lt;td&gt;安防监控&lt;/td&gt;
      &lt;td&gt;10.40.40.0/24&lt;/td&gt;
      &lt;td&gt;摄像头 ×4 + NVR（&lt;strong&gt;禁止外网&lt;/strong&gt;）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;V50&lt;/td&gt;
      &lt;td&gt;访客网络&lt;/td&gt;
      &lt;td&gt;10.50.50.0/24&lt;/td&gt;
      &lt;td&gt;来客 WiFi，完全隔离&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;wifi-ssid&quot;&gt;WiFi SSID&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;SSID&lt;/th&gt;
      &lt;th&gt;VLAN&lt;/th&gt;
      &lt;th&gt;说明&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Home&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;V20&lt;/td&gt;
      &lt;td&gt;家庭可信设备&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;IoT&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;V30&lt;/td&gt;
      &lt;td&gt;米家智能家居 WiFi 设备&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Guest&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;V50&lt;/td&gt;
      &lt;td&gt;访客 WiFi&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;一楼-1f&quot;&gt;一楼 (1F)&lt;/h2&gt;

&lt;h3 id=&quot;rg-eg210g-p-ev2--企业网关--8-lan口poe--2-wan口&quot;&gt;RG-EG210G-P-EV2 — 企业网关 · 8 LAN口(PoE) + 2 WAN口&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;端口&lt;/th&gt;
      &lt;th&gt;连接&lt;/th&gt;
      &lt;th&gt;备注&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;WAN①&lt;/td&gt;
      &lt;td&gt;上联 2F 交换机&lt;/td&gt;
      &lt;td&gt;Trunk All&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;WAN②&lt;/td&gt;
      &lt;td&gt;备用&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LAN①&lt;/td&gt;
      &lt;td&gt;吸顶AP ①&lt;/td&gt;
      &lt;td&gt;Trunk · 多SSID&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LAN②&lt;/td&gt;
      &lt;td&gt;吸顶AP ②&lt;/td&gt;
      &lt;td&gt;Trunk · 多SSID&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LAN③&lt;/td&gt;
      &lt;td&gt;TP-LINK TL-SG1005D&lt;/td&gt;
      &lt;td&gt;5口千兆·贴电视背面 → 电视 + PS5 + 中枢网关①&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LAN④&lt;/td&gt;
      &lt;td&gt;空闲&lt;/td&gt;
      &lt;td&gt;IPTV备用&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LAN⑤&lt;/td&gt;
      &lt;td&gt;靠墙备用网口&lt;/td&gt;
      &lt;td&gt;AP下方·电视移动备用位&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LAN⑥&lt;/td&gt;
      &lt;td&gt;房间网口 ①&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LAN⑦&lt;/td&gt;
      &lt;td&gt;房间网口 ②&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LAN⑧&lt;/td&gt;
      &lt;td&gt;GL.iNET MT3000&lt;/td&gt;
      &lt;td&gt;旁路由·TV/PS5手动设网关走翻墙&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;1F 客厅 · 中枢网关① + 小交换机&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;中枢网关①：小米 ZSWG01CM · 放电视背面 · DC圆孔电源(5V/1A)&lt;/li&gt;
  &lt;li&gt;下挂设备：TP-LINK TL-SG1005D 5口千兆交换机(¥89·贴电视背面) → 电视 + PS5 + 中枢网关①&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;⬇️ &lt;em&gt;Trunk All VLANs · 预埋网线&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;二楼-2f&quot;&gt;二楼 (2F)&lt;/h2&gt;

&lt;h3 id=&quot;rg-es218gc-p--16口千兆-poe-交换机--2-sfp光口--已用-13-口--备用-3-口&quot;&gt;RG-ES218GC-P — 16口千兆 PoE 交换机 + 2 SFP光口 · 已用 13 口 + 备用 3 口&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;端口&lt;/th&gt;
      &lt;th&gt;连接&lt;/th&gt;
      &lt;th&gt;备注&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;上联&lt;/td&gt;
      &lt;td&gt;← 1F 网关&lt;/td&gt;
      &lt;td&gt;Trunk All&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P①&lt;/td&gt;
      &lt;td&gt;吸顶AP ①&lt;/td&gt;
      &lt;td&gt;Trunk · 多SSID&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P②&lt;/td&gt;
      &lt;td&gt;吸顶AP ②&lt;/td&gt;
      &lt;td&gt;Trunk · 多SSID&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P③&lt;/td&gt;
      &lt;td&gt;摄像头 ①&lt;/td&gt;
      &lt;td&gt;PoE · V40&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P④&lt;/td&gt;
      &lt;td&gt;摄像头 ②&lt;/td&gt;
      &lt;td&gt;PoE · V40&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P⑤&lt;/td&gt;
      &lt;td&gt;摄像头 ③&lt;/td&gt;
      &lt;td&gt;PoE · V40&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P⑥&lt;/td&gt;
      &lt;td&gt;摄像头 ④&lt;/td&gt;
      &lt;td&gt;PoE · V40&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P⑦&lt;/td&gt;
      &lt;td&gt;衣帽间&lt;/td&gt;
      &lt;td&gt;V20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P⑧&lt;/td&gt;
      &lt;td&gt;主卧&lt;/td&gt;
      &lt;td&gt;V20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P⑨&lt;/td&gt;
      &lt;td&gt;小孩房 ①&lt;/td&gt;
      &lt;td&gt;V20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P⑩&lt;/td&gt;
      &lt;td&gt;小孩房 ②&lt;/td&gt;
      &lt;td&gt;V20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P⑪&lt;/td&gt;
      &lt;td&gt;书房 → NVR&lt;/td&gt;
      &lt;td&gt;V40&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P⑫&lt;/td&gt;
      &lt;td&gt;书房网口 ②&lt;/td&gt;
      &lt;td&gt;V20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P⑬&lt;/td&gt;
      &lt;td&gt;中枢网关②&lt;/td&gt;
      &lt;td&gt;弱电箱旁公共区域·3D打印PoE支架贴墙·PoE供电&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;P⑭⑮⑯&lt;/td&gt;
      &lt;td&gt;空闲备用 ×3&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;2F 弱电箱旁 · 中枢网关②&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;中枢网关②：小米 ZSWG01CM · 位置：弱电箱旁公共区域（楼层正中间，蓝牙Mesh 2.0覆盖最佳）&lt;/li&gt;
  &lt;li&gt;安装方式：3D打印PoE支架贴墙(¥34) · PoE分离器转DC供电 · 短网线从弱电箱穿出&lt;/li&gt;
  &lt;li&gt;覆盖范围：衣帽间→主卧→书房→弱电箱→过道→小孩房×2，总长22m，蓝牙Mesh 2.0每设备中继&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;书房 · NVR + 显示器&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;DS-7808N-Q1（无PoE）· VLAN 40 · ← 书房网线① ← 2F交换机 → 显示器&lt;/li&gt;
  &lt;li&gt;硬盘：翻新 4TB 监控盘 · 摄像头与NVR同VLAN，完全隔离&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;防火墙规则&quot;&gt;防火墙规则&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;规则&lt;/th&gt;
      &lt;th&gt;方向&lt;/th&gt;
      &lt;th&gt;说明&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;✅&lt;/td&gt;
      &lt;td&gt;V20 可信 → 全部&lt;/td&gt;
      &lt;td&gt;手机/电脑可控制智能家居、查看摄像头、管理网络&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;✅&lt;/td&gt;
      &lt;td&gt;中枢网关 IP → V30&lt;/td&gt;
      &lt;td&gt;单独放行 mDNS 中继，允许发现和控制 WiFi IoT 设备&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;❌&lt;/td&gt;
      &lt;td&gt;V30 IoT → V20/40&lt;/td&gt;
      &lt;td&gt;IoT 设备不能访问可信网络和摄像头，只能上网 + 同 VLAN&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;❌&lt;/td&gt;
      &lt;td&gt;V40 监控 → 外网&lt;/td&gt;
      &lt;td&gt;摄像头&lt;strong&gt;禁止访问外网&lt;/strong&gt;，仅与 NVR 同 VLAN 互通&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;❌&lt;/td&gt;
      &lt;td&gt;V50 访客 → 全部内网&lt;/td&gt;
      &lt;td&gt;访客只能上网，不能访问任何内网设备&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;🏠&lt;/td&gt;
      &lt;td&gt;断网可用&lt;/td&gt;
      &lt;td&gt;中枢网关×2 本地控制蓝牙Mesh 2.0设备，不受外网影响&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;安防费用汇总&quot;&gt;安防费用汇总&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;项目&lt;/th&gt;
      &lt;th&gt;型号&lt;/th&gt;
      &lt;th&gt;价格&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;摄像头 ×4&lt;/td&gt;
      &lt;td&gt;DS-2CD1245-LA · ¥195 × 4&lt;/td&gt;
      &lt;td&gt;¥780&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;NVR 录像机&lt;/td&gt;
      &lt;td&gt;DS-7808N-Q1（无PoE）&lt;/td&gt;
      &lt;td&gt;¥395&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;监控硬盘&lt;/td&gt;
      &lt;td&gt;翻新 4TB&lt;/td&gt;
      &lt;td&gt;¥750&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2F 交换机（升级）&lt;/td&gt;
      &lt;td&gt;RG-ES218GC-P 16口千兆PoE + 2SFP光口&lt;/td&gt;
      &lt;td&gt;¥1,196&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1F 小交换机&lt;/td&gt;
      &lt;td&gt;TP-LINK TL-SG1005D 5口千兆&lt;/td&gt;
      &lt;td&gt;¥89&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;旁路由&lt;/td&gt;
      &lt;td&gt;GL.iNET MT3000&lt;/td&gt;
      &lt;td&gt;¥499&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2F 中枢网关配件&lt;/td&gt;
      &lt;td&gt;3D打印PoE支架(贴墙)&lt;/td&gt;
      &lt;td&gt;¥34&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1F 交换机&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;不需要 ✅&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;NVR PoE 口&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;不需要（省 ¥87）✅&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;安防总计（不含交换机）&lt;/strong&gt;：¥1,925&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;额外配件合计&lt;/strong&gt;：¥622（小交换机 ¥89 + 旁路由 ¥499 + PoE支架 ¥34）&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;智能家居设备清单&quot;&gt;智能家居设备清单&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;领普 E3 Pro + 平头熊 + 米家&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;一楼-1f-1&quot;&gt;一楼 (1F)&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;房间&lt;/th&gt;
      &lt;th&gt;智能开关 (领普 E3 Pro)&lt;/th&gt;
      &lt;th&gt;窗帘电机 (平头熊)&lt;/th&gt;
      &lt;th&gt;音箱&lt;/th&gt;
      &lt;th&gt;其他&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;玄关&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 4键 ×1 ¥70 — 玄关灯+过厅灯+离家模式+全关/回家&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;人体传感器 ×1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;客厅&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 4键 ×1 ¥70 — 主灯+氛围灯+阳台灯+窗帘&lt;/td&gt;
      &lt;td&gt;平头熊 ×1 南落地窗&lt;/td&gt;
      &lt;td&gt;小爱音箱Pro ×1 红外遥控电视/空调&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;餐厅&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 3键 ×1 ¥70 — 主灯+餐边灯+窗帘&lt;/td&gt;
      &lt;td&gt;平头熊 ×1 南窗&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;厨房&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 单键 ×1 ¥50 — 主灯&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;公卫&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 2键 ×1 ¥60 — 主灯+排风扇&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;人体传感器 ×1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;麻将室&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 4键 ×1 ¥70 — 主灯+灯2+窗帘+场景&lt;/td&gt;
      &lt;td&gt;平头熊 ×2 北窗+西窗&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;卧室1（客房）&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 单键 ×1 ¥50 — 主灯（吸顶灯）&lt;/td&gt;
      &lt;td&gt;窗帘盒已预留电源，电机后续可加&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;卧室2（长辈房）&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 2键 ×1 ¥60 — 主灯+床头灯&lt;/td&gt;
      &lt;td&gt;窗帘盒已预留电源，电机后续可加&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;卧室2 内卫&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 2键 ×1 ¥60 — 主灯+排风扇&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;二楼-2f-1&quot;&gt;二楼 (2F)&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;房间&lt;/th&gt;
      &lt;th&gt;智能开关 (领普 E3 Pro)&lt;/th&gt;
      &lt;th&gt;窗帘电机 (平头熊)&lt;/th&gt;
      &lt;th&gt;音箱&lt;/th&gt;
      &lt;th&gt;其他&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;起居室&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 2键 ×1 ¥60 — 主灯+氛围灯&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;小爱音箱 ×1 标准版&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;主卧&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 4键 ×1 ¥70 — 主灯+床头灯+夜灯+窗帘&lt;/td&gt;
      &lt;td&gt;平头熊 ×1 南落地窗(建议双轨)&lt;/td&gt;
      &lt;td&gt;小爱音箱 闹钟版 ×1&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;衣帽间&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 单键 ×1 ¥50 — 主灯&lt;/td&gt;
      &lt;td&gt;窗帘盒已预留电源，电机后续可加&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;人体传感器 ×1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;主卫&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 3键 ×1 ¥70 — 主灯+镜前灯+窗帘&lt;/td&gt;
      &lt;td&gt;平头熊 ×1 卫生间窗&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;书房&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 2键 ×1 ¥60 — 主灯+台灯&lt;/td&gt;
      &lt;td&gt;窗帘盒已预留电源，电机后续可加&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;小孩房1&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 3键 ×1 ¥70 — 主灯+床头灯+窗帘&lt;/td&gt;
      &lt;td&gt;平头熊 ×1 北窗&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;小孩房2&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 3键 ×1 ¥70 — 主灯+床头灯+窗帘&lt;/td&gt;
      &lt;td&gt;平头熊 ×2 南飘窗+西窗&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;公卫&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;E3 Pro 单键 ×1 ¥50 — 主灯&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;—&lt;/td&gt;
      &lt;td&gt;人体传感器 ×1&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;设备数量汇总&quot;&gt;设备数量汇总&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;类别&lt;/th&gt;
      &lt;th&gt;数量&lt;/th&gt;
      &lt;th&gt;价格&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;智能开关 · 领普 E3 Pro&lt;/td&gt;
      &lt;td&gt;单键×4 + 2键×5 + 3键×4 + 4键×4 = &lt;strong&gt;17个&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;¥1,060&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;窗帘电机 · 平头熊&lt;/td&gt;
      &lt;td&gt;1F 4个 + 2F 3-4个，共 7-8 个，已有 4 个，补购 3-4 个&lt;/td&gt;
      &lt;td&gt;¥450-800&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;小爱音箱&lt;/td&gt;
      &lt;td&gt;Pro×1 + 闹钟版×1 + 标准版×1&lt;/td&gt;
      &lt;td&gt;¥500&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;中枢网关&lt;/td&gt;
      &lt;td&gt;小米 ZSWG01CM ×2&lt;/td&gt;
      &lt;td&gt;¥500&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;人体传感器&lt;/td&gt;
      &lt;td&gt;×5（玄关+公卫×2+衣帽间+客厅）&lt;/td&gt;
      &lt;td&gt;¥150&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;首批必装（不含窗帘电机补购）&lt;/strong&gt;：¥2,210（开关¥1,060 + 网关¥500 + 音箱¥500 + 传感器¥150）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;窗帘电机补购（分期加）&lt;/strong&gt;：¥450-800&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;智能家居全部到位&lt;/strong&gt;：¥2,660-3,010&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;⚠️ 施工提醒：&lt;/strong&gt; 中枢网关①放1F客厅电视背面，中枢网关②放2F弱电箱旁公共区域（3D打印PoE支架贴墙）&lt;/p&gt;
&lt;/blockquote&gt;
</content>
  </entry>
  
  <entry>
    <title>Creating Custom Stickers/Avatars from Your Webcam - Part 1</title>
    <link href="https://gonglexin.com/posts/poker-face-part-1"/>
    <updated>2024-03-04T00:00:00+00:00</updated>
    <id>https://gonglexin.com/posts/poker-face-part-1</id>
    <content type="html">&lt;p&gt;I’ve just made my new project,
&lt;a href=&quot;https://poker-face.gonglexin.com&quot;&gt;Poker Face&lt;/a&gt;,
available as &lt;a href=&quot;https://github.com/gonglexin/poker_face&quot;&gt;open-source&lt;/a&gt;.
This project is crafted with Elixir and Phoenix LiveView.
Its core features allow you to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Transform your webcam feed into stickers or avatars.&lt;/li&gt;
  &lt;li&gt;Pose questions about the camera images to GPT.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out the introduction video here: &lt;a href=&quot;https://www.youtube.com/watch?v=EFUJFZRQNFk&quot;&gt;&lt;img src=&quot;https://img.youtube.com/vi/EFUJFZRQNFk/0.jpg&quot; alt=&quot;Poker Face intro video&quot; /&gt;&lt;/a&gt;
In this blog series, I’ll guide you through every detail of the implementation.
Welcome to Part 1.&lt;/p&gt;

&lt;p&gt;In this very first part, we’ll tackle a couple of key steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Opening the camera and capturing the video frame.&lt;/li&gt;
  &lt;li&gt;Sending the frame to a LiveView process and interfacing it with Gemini or OpenAI.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;initial-project&quot;&gt;Initial Project&lt;/h3&gt;

&lt;p&gt;Let’s kick things off with setting up the project.
Navigate to your desired directory and create a new phoenix project:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;mix phx.new poker_face
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;From the outset, my goal was to build this entire project in LiveView.
I proceeded by adding &lt;code&gt;face_live.ex&lt;/code&gt; and &lt;code&gt;face_live.html.heex&lt;/code&gt;,
then configuring the router to point to this LiveView component.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-elixir&quot;&gt;defmodule PokerFaceWeb.FaceLive do
  use PokerFaceWeb, :live_view

  @impl true
  def mount(_prams, _session, socket) do
    {:ok, assign(socket, :photo_info, nil)}
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&quot;language-elixir&quot;&gt;&amp;lt;div&amp;gt;
  &amp;lt;.header&amp;gt;
    Poker Face
    &amp;lt;:subtitle&amp;gt;&amp;lt;/:subtitle&amp;gt;
  &amp;lt;/.header&amp;gt;
  &amp;lt;div class=&quot;grid grid-cols-2 gap-x-2&quot;&amp;gt;
    &amp;lt;div id=&quot;user_photo&quot; class=&quot;rounded shadow-lg p-2 flex flex-col gap-y-2&quot; phx-hook=&quot;Camera&quot;&amp;gt;
      &amp;lt;button
        id=&quot;startCamera&quot;
        class=&quot;p-4 w-full h-full disabled:cursor-not-allowed&quot;
      &amp;gt;
        &amp;lt;p class=&quot;select-none flex items-center gap-2&quot;&amp;gt;
          &amp;lt;.icon name=&quot;hero-camera&quot; /&amp;gt;Take a photo with your webcam
        &amp;lt;/p&amp;gt;
      &amp;lt;/button&amp;gt;
      &amp;lt;div class=&quot;flex flex-col gap-y-2&quot;&amp;gt;
        &amp;lt;video id=&quot;video&quot; class=&quot;hidden&quot; playsinline autoplay&amp;gt;&amp;lt;/video&amp;gt;
        &amp;lt;img id=&quot;photo&quot; /&amp;gt;

        &amp;lt;div id=&quot;buttonGroup&quot; class=&quot;hidden flex flex-col gap-y-2 justify-center&quot;&amp;gt;
          &amp;lt;button
            id=&quot;takePhoto&quot;
            class=&quot;rounded-full py-2 border border-black hover:bg-black hover:text-white&quot;
          &amp;gt;
            Take Photo
          &amp;lt;/button&amp;gt;
          &amp;lt;button
            id=&quot;stopCamera&quot;
            class=&quot;rounded-full py-2 border border-black hover:bg-black hover:text-white&quot;
          &amp;gt;
            Cancel
          &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;canvas id=&quot;canvas&quot; class=&quot;hidden&quot;&amp;gt;&amp;lt;/canvas&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div id=&quot;poker_photo&quot; class=&quot;rounded shadow-lg p-2&quot;&amp;gt;
      &amp;lt;p class=&quot;text-center text-lg&quot;&amp;gt;&amp;lt;%= @photo_info %&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&quot;language-elixir&quot;&gt;defmodule PokerFaceWeb.Router do
  use PokerFaceWeb, :router

  ...

  scope &quot;/&quot;, PokerFaceWeb do
    pipe_through :browser

    live &quot;/&quot;, FaceLive
  end

  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once you run &lt;code&gt;mix phx.server&lt;/code&gt;, your page should like this: &lt;img src=&quot;/assets/images/poker-face/1.png&quot; alt=&quot;inital-page&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The variable &lt;code&gt;@photo_info&lt;/code&gt; is designated to display the responses
from GPT/Gemini once we’ve sent an video frame to be analyzed.&lt;/p&gt;

&lt;h3 id=&quot;webcam&quot;&gt;Webcam&lt;/h3&gt;

&lt;p&gt;Now, let’s delve into the webcam functionality.
To be honest, webcams were somewhat foreign to me.
So, I turned to GPT for assistance on how to implement it,
and it provided a solid, functional code example.
All I had to do was integrate the JavaScript into a Phoenix Hook.
&lt;img src=&quot;/assets/images/poker-face/2.png&quot; alt=&quot;ask_gpt&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Add this code to &lt;code&gt;app.js&lt;/code&gt; with any necessary modifications:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let Hooks = {}

Hooks.Camera = {
  mounted() {
    const startCamera = document.getElementById(&apos;startCamera&apos;);
    const video = document.getElementById(&apos;video&apos;);
    const takePhoto = document.getElementById(&apos;takePhoto&apos;);
    const stopCamera = document.getElementById(&apos;stopCamera&apos;);
    const canvas = document.getElementById(&apos;canvas&apos;);
    const buttonGroup = document.getElementById(&apos;buttonGroup&apos;);
    const photo = document.getElementById(&quot;photo&quot;);
    let stream = null;

    // Start the camera when button is clicked
    startCamera.addEventListener(&apos;click&apos;, async () =&amp;gt; {
      stream = await navigator.mediaDevices.getUserMedia({ video: true });
      video.srcObject = stream;
      video.classList.remove(&apos;hidden&apos;);
      buttonGroup.classList.remove(&apos;hidden&apos;);
      startCamera.classList.add(&apos;hidden&apos;);
      photo.src = &quot;&quot;;
    });

    // Take a photo
    takePhoto.addEventListener(&apos;click&apos;, () =&amp;gt; {
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      canvas.getContext(&apos;2d&apos;).drawImage(video, 0, 0);
      const imageDataURL = canvas.toDataURL(&apos;image/png&apos;);
      photo.src = imageDataURL;

      // push to the back-end and interact with Gemini/GPT
      this.pushEvent(&quot;new_photo&quot;, { photo: imageDataURL })

      // buttonGroup.classList.add(&apos;hidden&apos;);
      // startCamera.classList.remove(&apos;hidden&apos;);
      // video.classList.add(&apos;hidden&apos;);
      // video.srcObject = null;
    });

    // Stop the camera
    stopCamera.addEventListener(&apos;click&apos;, () =&amp;gt; {
      if (stream) {
        stream.getTracks().forEach(track =&amp;gt; track.stop());
      }
      buttonGroup.classList.add(&apos;hidden&apos;);
      startCamera.classList.remove(&apos;hidden&apos;);
      video.classList.add(&apos;hidden&apos;);
      video.srcObject = null;
      photo.src = &quot;&quot;;
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;By clicking the camera button, the camera will activate: &lt;img src=&quot;/assets/images/poker-face/3.png&quot; alt=&quot;camera&quot; /&gt;
Cool, it works. GPT is great!&lt;/p&gt;

&lt;p&gt;Click the ‘Take Photo’ button though, and you’ll bump into an error.
Currently, when this button is clicked,
an event named &lt;code&gt;new_photo&lt;/code&gt; is dispatched to the backend,
for which the handling is not yet implemented.
Let’s proceed with that.&lt;/p&gt;

&lt;h3 id=&quot;liveview-process&quot;&gt;LiveView process&lt;/h3&gt;

&lt;p&gt;Edit &lt;code&gt;face_live.ex&lt;/code&gt; and add these functions:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-elixir&quot;&gt;...

  @impl true
  def handle_event(&quot;new_photo&quot;, %{&quot;photo&quot; =&amp;gt; photo}, socket) do
    send(self(), {:anaylse, photo})
    {:noreply, socket}
  end

  @impl true
  def handle_info({:anaylse, photo}, socket) do
    {:ok, text} = Gemini.analyze_image(photo)

    socket =
      socket
      |&amp;gt; assign(:photo_info, text)

    {:noreply, socket}
  end

...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Instead of OpenAI gpt, We implement Gemini API first, Let’s add a &lt;code&gt;gemini.ex&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-elixir&quot;&gt;defmodule PokerFace.Gemini do
  @vision_uri &quot;https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent&quot;

  def analyze_image(&quot;data:image/png;base64,&quot; &amp;lt;&amp;gt; image_data) do
    prompt = &quot;&quot;&quot;
      What&apos;s is he/she/it doing?
    &quot;&quot;&quot;

    body = %{
      contents: [
        %{
          parts: [
            %{text: prompt},
            %{inlineData: %{mimeType: &quot;image/png&quot;, data: image_data}}
          ]
        }
      ]
    }

    resp =
      Req.post!(@vision_uri &amp;lt;&amp;gt; &quot;?key=#{System.get_env(&quot;GOOGLE_AI_API_KEY&quot;)}&quot;,
        json: body,
        receive_timeout: 60_000,
        connect_options: [protocols: [:http1]]
      )

    text =
      resp.body[&quot;candidates&quot;]
      |&amp;gt; List.first()
      |&amp;gt; Map.get(&quot;content&quot;)
      |&amp;gt; Map.get(&quot;parts&quot;)
      |&amp;gt; List.first()
      |&amp;gt; Map.get(&quot;text&quot;)

    {:ok, text}
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In order to utilize Gemini, you must obtain your personal API key from &lt;a href=&quot;https://ai.google.dev/&quot;&gt;Google AI&lt;/a&gt;.
Once acquired and configured as an environment variable,
you’ll possess a functional AI-powered image analyzer.
&lt;img src=&quot;/assets/images/poker-face/4.png&quot; alt=&quot;gemini response&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Although Gemini responds correctly,
the photo captured becomes non-persistent on the current page.
So, we need to make some fix and an OpenAI version could also be integrated.
I’ll not show the code here.
But you can check all the
code &lt;strong&gt;&lt;a href=&quot;https://github.com/gonglexin/poker_face/compare/0db4a57ee0d31d4d34da32acb181a121655379b9...1b60c1ded174555449a1ab89eb8d5eb16e881cde&quot;&gt;changes&lt;/a&gt;&lt;/strong&gt;
We should make today.&lt;/p&gt;

&lt;p&gt;At the end of these modifications,
we are equipped with a fully operational camera AI image analyzer!
&lt;img src=&quot;/assets/images/poker-face/5.png&quot; alt=&quot;5.png&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Managing Elixir's main branch with asdf</title>
    <link href="https://gonglexin.com/posts/managing-elixir-main-branch-with-asdf"/>
    <updated>2024-01-10T00:00:00+00:00</updated>
    <id>https://gonglexin.com/posts/managing-elixir-main-branch-with-asdf</id>
    <content type="html">&lt;p&gt;I’ve always used &lt;a href=&quot;https://github.com/asdf-vm/asdf&quot;&gt;asdf&lt;/a&gt; to manage Elixir versions, and I’ve also had a persistent need to run projects or experiments with the latest code from the Elixir repo (main branch). Installing the main branch is quite simple:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Clone the Elixir code and build it locally:
    &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/elixir-lang/elixir.git /path/to/elixir
cd /path/to/elixir &amp;amp;&amp;amp; make
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;Create a symlink in the asdf installs directory and switch to the main version:
    &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ln -s /path/to/elixir ~/.asdf/installs/elixir/main
asdf reshim elixir main
asdf shell elixir main | asdf global elixir main
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;Verify:
    &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ asdf list elixir
  1.15.7-otp-26
  1.16.0-otp-26
 *main
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;Daily update:
    &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /path/to/elixir &amp;amp;&amp;amp; git pull &amp;amp;&amp;amp; make
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;You can write the above  update commands to a script and run it periodically to ensure you’re always using the latest version of the main branch.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;If running make directly throws compilation errors, you need to delete the previous build artifacts before rebuilding:
    &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /path/to/elixir
make clean &amp;amp;&amp;amp; make
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
  &lt;li&gt;Using the main branch can be somewhat unstable as it’s still under development.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>在安卓手机上安装 Elixir</title>
    <link href="https://gonglexin.com/posts/install-elixir-on-android"/>
    <updated>2019-07-25T00:00:00+00:00</updated>
    <id>https://gonglexin.com/posts/install-elixir-on-android</id>
    <content type="html">&lt;p&gt;记录一下在安卓手机上安装 &lt;a href=&quot;https://elixir-lang.org/&quot;&gt;Elixir&lt;/a&gt; 的过程。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;在手机上安装 &lt;a href=&quot;https://termux.com/&quot;&gt;Termux&lt;/a&gt; 应用&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;开启 SSH 服务（&lt;strong&gt;非必须&lt;/strong&gt;, 开 &lt;code&gt;ssh&lt;/code&gt; 只是为了便于 PC 端访问操作手机，比在手机上打字方便）
    &lt;ul&gt;
      &lt;li&gt;安装相关包
        &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  apt update
  apt upgrade
  pkg install openssh
  pkg install net-tools
&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
      &lt;li&gt;设置密码并开启 ssh
        &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  # 设置一个密码
  passwd
  # 开启服务
  sshd
&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
      &lt;li&gt;查看手机 ip
        &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  ifconfig
&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;安装 Elixir
    &lt;ul&gt;
      &lt;li&gt;从 PC 端登录手机
        &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  # 确保手机和 PC 处在同一网络，用上一步获取的安卓手机 ip 登录
  # 拷贝公钥到安卓手机(需输入前面设置的密码)
  ssh-copy-id -p 8022 -i your_public_key_file andriod_ip
  # 登录
  ssh -p 8022 android_ip
&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
      &lt;li&gt;安装相关包 (PC 端登录之后，就可以开始正式的安装步骤了)
        &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  pkg install unzip
  pkg install erlang
&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
      &lt;li&gt;安装 Elixir, 参考 &lt;a href=&quot;https://github.com/hexpm/bob#elixir-builds&quot;&gt;https://github.com/hexpm/bob#elixir-builds&lt;/a&gt; 确定好 elixir 和 erlang otp 版本, 这里以 elixir 1.9.1 otp 22 为例:
        &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  mkdir elixir
  cd elixir
  # wget https://repo.hex.pm/builds/elixir/{REF}-otp-{OTP_MAJOR_VERSION}.zip
  wget https://repo.hex.pm/builds/elixir/v1.9.1-otp-22.zip
  unzip v1.9.1-otp-22.zip
  rm v1.9.1-otp-22.zip
&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
      &lt;li&gt;设置 PATH, 保存到 bashrc
        &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  vi .bashrc
  export PATH=$PATH:$HOME/elixir/bin
&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;验证
    &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;elixir -v
#&amp;gt; Erlang/OTP 22 [erts-10.4.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1]
#&amp;gt;
#&amp;gt; Elixir 1.9.1 (compiled with Erlang/OTP 22)
&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>Mac 下临时显示隐藏的文件和文件夹</title>
    <link href="https://gonglexin.com/posts/how-to-temporarily-display-hidden-files-and-folders-in-mac-os-x"/>
    <updated>2013-12-21T00:00:00+00:00</updated>
    <id>https://gonglexin.com/posts/how-to-temporarily-display-hidden-files-and-folders-in-mac-os-x</id>
    <content type="html">&lt;p&gt;今天遇到一个问题, 在使用 Github 的 Mac 客户端的时候, 想直接导入已经 clone 到本地的 repo, 但是这个 repo 目录是一个以 . 为开头的格式命名的隐藏目录, 在打开的文件选择窗口里面是无法看到的。&lt;/p&gt;

&lt;p&gt;以前我一般是打开终端输入如下命令:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;defaults write com.apple.finder AppleShowAllFiles TRUE; killall Finder
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这条命令有一个不便利的地方就是执行之后系统就会一直显示那些隐藏文件，往往等你操作完之后，你又需要把值重新设置回去:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;defaults write com.apple.finder AppleShowAllFiles FALSE; killall Finder
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;今天才发现，原来在选择文件的时候, 只需要按下 &lt;strong&gt;&lt;code&gt;Command + Shift + .&lt;/code&gt;&lt;/strong&gt; 组合键就可以临时地显示隐藏文件了。当然这不仅仅适用于这一个软件，Mac 下所有软件的文件选择窗口都是可以这么做的。&lt;/p&gt;

&lt;p&gt;参考: &lt;a href=&quot;http://www.tekrevue.com/tip/how-to-temporarily-see-hidden-files-folders-in-mac-os-x/&quot;&gt;http://www.tekrevue.com/tip/how-to-temporarily-see-hidden-files-folders-in-mac-os-x/&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Rails 使用 mongoDB 和 Slim</title>
    <link href="https://gonglexin.com/posts/rails-project-setup-for-mongodb-and-slim"/>
    <updated>2012-11-24T00:00:00+00:00</updated>
    <id>https://gonglexin.com/posts/rails-project-setup-for-mongodb-and-slim</id>
    <content type="html">&lt;p&gt;预言中的世界末日都要来了，期待的 &lt;code&gt;Ruby 2.0&lt;/code&gt; 和 &lt;code&gt;Rails 4.0&lt;/code&gt; 都还没发布。那我就来看看怎么集成 &lt;a href=&quot;http://www.mongodb.org/&quot;&gt;mongoDB&lt;/a&gt; 和 &lt;a href=&quot;http://slim-lang.com/&quot;&gt;Slim&lt;/a&gt; 到一个 Rails 工程。&lt;/p&gt;

&lt;h2 id=&quot;安装-mongodb-数据库&quot;&gt;安装 mongoDB 数据库&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;brew install mongodb&lt;/code&gt;
安装好之后，可以把 mongo 的服务设为开机启动，或者在需要的时候手动 &lt;code&gt;mongod&lt;/code&gt; 开启。&lt;/p&gt;

&lt;h2 id=&quot;新建-rails-工程&quot;&gt;新建 Rails 工程&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;rails new projectname --skip-test-unit --skip-active-record --skip-bundle
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因为使用 mongoDB 并打算使用 &lt;a href=&quot;https://github.com/seattlerb/minitest&quot;&gt;minitest&lt;/a&gt; 所以会在创建工程的时候 skip 掉不必要的东西。&lt;/p&gt;

&lt;h2 id=&quot;修改-gemfile&quot;&gt;修改 Gemfile&lt;/h2&gt;

&lt;p&gt;在 Gemfile 里面添加以下几句之后执行 &lt;code&gt;bundle install&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;gem &apos;slim-rails&apos;
gem &apos;mongoid&apos;
group :test, :development do
  gem &apos;minitest-rails&apos;
end
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;生成数据库配置和-minitest-helper-文件&quot;&gt;生成数据库配置和 minitest helper 文件&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;rails generate mongoid:config
rails generate mini_test:install
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;slim-页面&quot;&gt;Slim 页面&lt;/h2&gt;

&lt;p&gt;把 application.html.erb 转换成 slim 格式，可以使用 &lt;a href=&quot;http://html2slim.herokuapp.com/&quot;&gt;html2slim&lt;/a&gt; 或者自己手动修改。参见我改好的&lt;a href=&quot;https://gist.github.com/4139805&quot;&gt;版本&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;使用&quot;&gt;使用&lt;/h2&gt;

&lt;p&gt;现在就可以使用 &lt;code&gt;rails g model | controller&lt;/code&gt; 等等命令了，默认生成的 view 文件都会是 slim 格式，model 都会自动 include &lt;a href=&quot;https://github.com/mongoid/mongoid&quot;&gt;mongoid&lt;/a&gt; 的 class。enjoy it!&lt;/p&gt;
</content>
  </entry>
  

</feed>
