掃地機器人裡的 Ubuntu:用 Valetudo 實現完全去雲端化的 Linux 自走車

多年前買了小米掃地機器人之後,我一直很好奇它到底在背景偷偷把什麼東西傳回伺服器。為了揭開這個黑盒子,我曾嘗試將電腦偽裝成 SSID 的 Gateway,透過 ARP 欺騙 (ARP spoofing) 的方式攔截封包。雖然成功拿到了數據,但除了目的地之外,封包內容依然處於加密狀態,難以直接解密。

更令人不安的是,這台機器人傳輸封包的頻率極高,它可是一直傳、一直傳,這種「不受控」的感覺實在令人不爽。直到後來,我偶然發現了 Valetudo 方案,這才解決了我的隱私焦慮。

技術核心:在機器人大腦裡植入「寄生蟲」

簡單來說,Valetudo 就像是在掃地機器人的系統裡放了一個寄生蟲。它會攔截原本要送往遠端伺服器的資料,將其導流至機器人內部的本地空間處理,隨後便銷毀與雲端的聯繫。這讓你的掃地機器人徹底脫離雲端監控,變成一台純粹的區網設備。

安裝流程與環境準備

安裝過程其實不算複雜,但對網路環境的要求較高。以下是我的實作筆記:

1. 回復原廠設定

首先,需要將機器人重置:先按下 Home 鍵,接著拿迴紋針戳一下 Reset 鍵後放開。過一陣子,機器人會發出語音提示正在回復原廠設定,大約需要五分鐘的時間。

2. 下載必要程式

  • 燒錄工具:valetudo-helper-miioota 下載對應作業系統的執行檔。
  • 自定義韌體 (寄生蟲):DustBuilder 找到支援的型號並構建版本。

以我的小米掃地機器人一代為例,我使用了 這個專屬連結。在構建過程中,需要填入個人資訊與 SSH 公鑰(Key),這樣後續才能直接透過終端機遠端登入。

3. 系統環境的坑

在燒錄工具的選擇上,我一開始在 Windows 環境下屢試不爽,推測是防火牆或網路設定干擾。後來改用 Linux (AMD64) 筆電,關閉防火牆與 Tailscale 等網路服務後,一次就成功了。

執行燒錄指令

待機器人重置完成並連上它的臨時 Wi-Fi 後,執行以下指令開始植入:

./valetudo-helper-miioota-linux-x64 install-firmware v11_004018.pkg

不到 10 分鐘,Valetudo 就植入完成了!現在我不僅能使用網頁介面操作,還能直接透過 SSH 登入機器人的後台。

開箱掃地機器人的「內臟」

登入後,我驚訝地發現這台機器的規格相當高規格:

  • 作業系統: Ubuntu 14.04.3 LTS (Trusty Tahr)
  • 處理器: Allwinner A33 低功耗四核 ARM Cortex-A7

這本質上就是一台 Linux 自走車!這也解釋了為什麼它能記錄這麼多隱私資訊。在未「去雲端化」前,它會上傳以下三類資料:

  1. 空間隱私: 透過 LDS 雷達掃描出的 2D 點雲圖,包含家具擺設、移動路徑與房間精確尺寸。
  2. 設備與網路資訊: 定期回報周邊 Wi-Fi SSID 和 MAC 位址,透過「Wi-Fi 指紋定位」即可精確掌握住家經緯度。
  3. 使用者行為: 清掃規律與偏好模式,這足以推斷家中何時無人。

去雲端化前後對比

當你刷入 Valetudo 後,你實際上是在機器內部建立了一道「數位邊界」。以下是處理前後的差異:

資料類型 官方韌體 (上傳北京) Valetudo (Root 後)
地圖數據 上傳伺服器再回傳手機 留在內存,由網頁直接讀取
Wi-Fi 掃描 定期回報周邊環境 攔截,資料僅存於本地日誌
OTA 更新 廠商強制或提示更新 徹底關閉,掌握主控權
通訊加密 伺服器持有 Key 你持有 Token,區網內連線

/mnt/data/rockrobo/rrlog 看到的資料夾,原本是這些敏感資料上傳前的「暫存區」,現在它們哪裡也去不了了。

進階探索:AI Agent 與移動控制實驗

掌握了這台 Linux 自走車後,我開啟了一個 AI Agent,讓它透過 SSH 在機器人內部到處巡檢,並撰寫 Script 驗證各種溝通方式。這部分的技術細節我也整理在我的專案中:

Roborock V1 Research - GitHub Project

這個專案不僅揭露了軟硬體結構,還包含如何透過 Python、API 與 MQTT 與它溝通。我最近做了一個有趣的實驗:

  • 指令發送: 利用 HTTP API 命令機器人移動到指定地點。
  • 狀態回饋: 利用 MQTT 即時回報機器人的當前座標。

結合這兩個特性,我已經能寫 Script 讓它走出特定的路徑,像是三角形、甚至正八邊形。相比起自己找材料組裝的自走車,這台機器人的感測器與底盤配備根本超棒。

結語:CP 值極高的開發平台

目前二手市場的小米掃地機器人一代大約只要 1000 元台幣 左右。如果您對 Linux 開發、自動導航或自走車專案有興趣,這絕對是一個物超所值的硬體平台。只要花一點心思刷入 Valetudo,它就不再是隱私外洩的潛在漏洞,而是一個完全受你掌控的強力研究工具。

從雲端回歸在地:我的 Raspberry Pi 5 x Moodle 5.1 實戰之路

我與 Moodle 的教學應用始於 2014 年。當時得益於學校資訊組長的協助,在校內架設了專屬伺服器,為全校提供數位學習服務。然而,隨著教育體系推動主機「向上集中化」,原本校內自主管理的 Moodle 隨之關閉,我的Moodle教學模式也因此中斷了一陣子。

後來,新竹縣教網辛老師推動了「M3 教育雲合作備課平台」,讓我得以接續使用。但在使用過程中,我逐漸萌生了「自主掌控」的想法。為了追求更高的客製化自由度、能隨心所欲地安裝外掛或調整版面,我決定探索「自架站點」的可能性,重新找回對教學系統的完全掌握權。

尋找最適合教學現場的方案

起初曾考慮租用商業雲端主機,但考量到教學需求並非 24 小時高流量,高峰期多集中在「線上測驗」時段,商業主機的持續維護成本相對較高。我也研究過的 MoodleBox,雖然它開箱即用、極為便利,但其預設將樹莓派改裝為獨立無線基地台(SSID)的模式,與我現有的教室網路架構不符。

最終,我決定挑戰「純手工架設」:從底層作業系統開始,在樹莓派上搭建專屬環境。這不僅能完全符合我的特殊需求,更能享受伺服器管理的過程,想要看看會撞什麼牆,而就算失敗也沒有損失。

針對高效能與耐用度的頂規硬體配置

為了應對測驗時 30-40 人同時送出答案的併發讀寫需求,我捨棄了效能與壽命較弱的 microSD 卡,採用了固態硬碟的儲存硬碟方案。主機選用 Raspberry Pi 5 Model B (16GB RAM),這是目前系列中記憶體最大的版本,確保在高負載測驗時,所有的 PHP 程序與資料庫快取都有充足的運算緩衝。

儲存方面,我透過 PCIe Gen 3 介面加裝了 512GB M.2 2230 NVMe SSD,實測讀取速度突破 700 MB/s。這徹底解決了資料庫讀寫的瓶頸,讓學生在切換測驗頁面時幾乎達到秒進的極速反應。再搭配官方主動散熱風扇,長時間高頻率運算,系統依然穩定不降速。

環境部署與網路邏輯的取捨

在網路架構上,我利用 mDNS 技術設定了 .local 自動識別網址。在校園內網中,學生只需輸入 http://bio.local 即可進入站台。這種「隱身內網」的配置,雖然無法從校外存取,但能有效隔絕來自公網的自動化攻擊。對於目前「學生不需在家測驗」的需求而言,是兼顧安全與便利的最佳解。(所以以上網址各位點了也沒用)

軟體核心採用標準且穩定的 LAMP (Apache, MariaDB, PHP 8.2) 架構,並針對 Moodle 5.1 進行了性能微調。版本控制則改採 Git 方式部署,捨棄傳統壓縮包,這讓未來的安全性更新與版本追蹤都能透過簡潔的指令無痛完成,大幅降低了長期維護的難度。

現代化架站秘訣:用 AI 當助手

站台架好已經一年了,架站過程中有大量 Linux 指令,一定會讓人看了就害怕,但事實上,現在有了 AI 的協助,一切都變得很簡單。不需要死背指令或翻文件,只需將精確的需求告訴 AI,它就會產出對應的步驟。我的工作變成了:提出需求、複製 AI 產出的指令、貼進終端機,若遇到報錯就再貼回給 AI 進行診斷。完成一部分工作後,再讓它寫md給我做紀錄

雖然我沒試過,但是我相信現在可以直接在 Linux 機器上利用 AI Agent直接完成所有設定,例如安裝opencode或GEMINI CLI之類的AGENT來直接用終端機下指令,因為 99% 的架站工作都是在終端機內完成,直接可以完成排錯與優化。

測距桿觀察海上物體有多遠

去看海時,你會不會好奇那艘船有多遠?

當我們站在岸邊或甲板上遠眺,如果要知道海面上的船隻或是其他物體的距離,光靠目測是非常不可靠的。這時候你需要一根「測距桿」來幫你測量距離。


一、 公式來源:Heinemann 的鳥類觀測研究

這套精確測距系統的核心公式,源自於 D. Heinemann 於 1981 年發表在《野生動物管理期刊》的研究:"A range finder for pelagic bird censusing"。這篇文章解決了海上觀測最大的難題:地球是圓的。在長距離觀測中,海平面會向下彎曲,Heinemann 引入了「視地平線」概念,確保了測量的嚴謹性。


二、 幾何推論:從「平面」到「曲面」

1. 基礎階段:假設地球是平的
若海面是平的,我們利用相似三角形原理:眼睛為頂點,臂長與木桿構成小三角形,眼高與距離構成大三角形。比例關係為 $x / b = h / d$,簡單直觀。

眼高 h 目標距離 d 臂長 b 下降高度 x 情境 A:理想平面模型

2. 進階階段:現實的曲面修正
現實中,地平線並非無限遠。如論文原圖所示,視線會與地球弧面相切。木桿頂端對準的是「視地平線 $v$」,而目標 $d$ 位於地平線「之下」。這兩條視線在木桿上截出的間距,就是我們需要的精確刻度 $C_i$。

臂長 b 視地平線 (v) 目標物 (d) Ci (修正刻度) 情境 B:Heinemann 曲面修正模型

三、 修正公式與代號意義

$C_i = \frac{b \cdot h \cdot (v - d)}{v \cdot d}$

  • $C_i$ (Calliper Interval): 刻度間距。木桿頂端向下量到目標刻度的距離。
  • $b$ (Arm's Length): 臂長。眼睛到木桿的水平距離(單位:公尺)。
  • $h$ (Eye Height): 眼高。眼睛距離水面的垂直高度(單位:公尺)。
  • $d$ (Distance to object): 目標物距離。你想要標註的特定距離。
  • $v$ (Visual Horizon): 視地平線距離。計算為 $3838 \times \sqrt{h}$。

四、 DIY 製作與實測

  1. 獲取個人參數:
    • 測量你的臂長 $b$(通常在 0.6m 到 0.8m 之間)。
    • 測量觀測位置的眼高 $h$(若人在岸邊且身高 1.7m,眼高約 1.6m;若在船上需加上甲板高度)。
  2. 精確標註: 以 $d=450m, h=2m, b=0.7m$ 為例:
    • 計算所得:$C_i \approx 0.285$ cm。
    • 製作建議:取 0.3 cm (3mm) 為標註基準。
  3. 實體製作:
    • 準備約 15~20cm 的平直木條,頂端劃紅線作為「地平線基準」。
    • 由頂端向下精確量測 0.3cm 並劃線,標記為「450m」。
  4. 海上實作: 伸直手臂,將木桿頂端對齊海平線。若目標物底端出現在 450m 線以下,即代表距離低於 450m。

五、 同場加映:如何在 Excel 計算?

如果你需要製作多種測距桿,可以使用 Excel 來計算。請參考以下步驟:





設定欄位名 (A欄):

  • A1: 眼高 h (公尺)
  • A2: 臂長 b (公尺)
  • A3: 目標距離 d (公尺)
  • A4: 視地平線 v
  • A5: 刻度間距 C_i (公分)

輸入數值與公式(B欄):

  • B4 (計算地平線): =3838 * SQRT(B1)
  • B5 (計算刻度並轉為公分): =B2*B1*(B4-B3)/(B4*B3)*100

為Garmin手錶開發Poincaré HRV Visualizer 觀察自律神經的即時變化

延續前一篇開發的「即時心率顯示圖表」並順利上架後,我打算重啟一個三年前的挑戰,在手錶上繼續開發心率變異性(HRV)的圖表

這項計畫的核心在於分析「連續兩次心跳的時間間隔」(RR Interval),藉此觀察人體生理狀態的微妙變化。目前主流穿戴裝置(如 Apple Watch、Garmin)都有內建 HRV 偵測,並以此推算使用者的壓力指數。

(註:雖然在穿戴裝置上我們習慣稱之為 RR Interval,但由於手錶多是透過 PPG 光學感測器偵測血流脈動,而非直接量測心臟電位,因此在生理訊號處理上,更精確的說法是偵測脈搏波峰之間的 Peak-to-Peak (PP) Interval 或 NN Interval。)

而我這次的目標,是直接利用這些心跳間隔數據,繪製出能展示心臟動態規律的 龐加萊圖(Poincaré Plot)

什麼是龐加萊圖?

假設我們測得的一連串心跳間隔(單位為 ms)為 $RR = \{RR_1, RR_2, RR_3, \dots, RR_n\}$。

我們可以將「本次間隔 $RR_n$」作為 $X$ 軸,「下次間隔 $RR_{n+1}$」作為 $Y$ 軸,組成一系列座標點:

$$ (x, y) = (RR_n, RR_{n+1}) $$

將這些點繪製在二維空間中,就能觀察到心跳與心跳之間的「動態演變」,這反映了自律神經系統中交感神經副交感神經的拉鋸過程:

  • 當交感神經占優勢(壓力大、運動中): 心跳會變得非常規律,每次間隔趨於一致。此時座標點會緊密凝聚在 $y = x$ 的對角線上。



  • 當副交感神經活躍(放鬆、休息): 心臟調節的靈活性增加,導致心跳間隔產生波動(HRV 較高)。此時點位會顯得分散,形成類似橢圓形的雲狀分布。



我設計的程式提供三種模式(Wide, Zoom, Auto),針對 Poincaré 散佈圖提供不同的「視角」:

1. Wide Mode (廣角模式):大局觀

  • 心率範圍:固定在 50 - 130 BPM
  • 使用時機:最適合一般走動或輕度活動。它提供了一個穩定的參考系,讓你一眼看出當前心跳是在平均範圍內,還是正處於劇烈變動。


2. Zoom Mode (縮放模式):精細度

  • 心率範圍:固定在 60 - 90 BPM
  • 使用時機:專為靜坐、冥想或深度放鬆設計。在這個區間,微小的心跳間期差異會被放大顯示,讓你更容易捕捉到每一次深呼吸帶來的副交感神經脈衝。


3. Auto Mode (自動縮放):全視角

  • 心率範圍:動態調整。App 會自動根據過去 60 點的數據極值,計算出最適合的顯示範圍。
  • 使用時機:如果你發現自己的心跳超出了 Wide/Zoom 的預設範圍,或者想要追求「最高的細節度」,Auto Mode 是最佳選擇。它能確保所有點始終保持在畫面中央,並佔據最大顯示面積。




其他數據:SD1、SD2、R 與 A

在 Poincaré 圖左上角即時更新的數據,是透過將心跳點群旋轉 45 度後的幾何投影計算得出的。



1. SD1 (短軸):副交感神經的「即時煞車」

  • 計算公式: $$ SD1^2 = \frac{1}{2} Var(RR_n - RR_{n+1}) $$ 幾何上,它是垂直於 $y=x$ 直線的標準差。
  • 生理意義:主要受迷走神經(副交感神經)調控。反映了身體在極短時間內調整心跳的能力。當你進行深呼吸時,SD1 會迅速增加。數值越高,代表副交感神經越活躍,身體正處於高效的修復狀態。

2. SD2 (長軸):自律神經的「總體引擎」

  • 計算公式: $$ SD2^2 = 2 Var(RR_n) - \frac{1}{2} Var(RR_n - RR_{n+1}) $$ 幾何上,它是沿著 $y=x$ 直線方向的標準差。
  • 生理意義:反映生理系統的總體頻寬,受交感與副交感共同影響。SD2 代表你的「生理韌性」。若 SD2 過低,代表心跳變得極度規律且僵硬(Rigid),可能暗示過度訓練或慢性壓力。

3. R (Ratio) - 自律神經平衡比

  • 計算公式: $$ R = \frac{SD1}{SD2} $$
  • 代表意義:反映「煞車」與「油門」的相對強度。
    • 低比例 ($R < 0.5$):通常出現在運動中或極大壓力下,代表交感神經過旺。
    • 中等比例 ($0.5 \le R \le 0.7$):理想的安靜休息狀態。
    • 高比例 ($R > 0.8$):極度放鬆或迷走神經亢進。

4. A (Area) - 總體能量 (橢圓面積)

  • 計算公式: $$ A = \pi \cdot SD1 \cdot SD2 $$
  • 代表意義:Poincaré 橢圓所覆蓋的面積,反映心血管系統總體調節能力的強弱。面積越大,代表心血管系統具備越高的韌性與應變能力。





其他圖表解析

除了三張不同縮放比例的Poincaré 圖外,另外還有兩張圖

Autonomic State Map (ASM):神經態勢圖

橫座標是SD1,縱座標是SD2,畫面中有一個白色參考點為 (60秒的平均) ,橙色點則為即時的狀態 (最近15秒內的計算結果)。斜線標示了SD1=SD2的直線。

Energy vs Balance (能量平衡圖):抗壓儲備指標

  • X 軸為平衡比 SD1/SD2的比,Y 軸為面積面積,但以對數顯示 (Area Log Scale)。



  • 圖表格線
    • X 軸兩條格線是 SD1/SD2=0.5 與 SD1/SD2=1.0 :這是判斷平衡的標尺。如果你在Poincaré 圖的橢圓沿著越扁,那麼在這張圖上你的點就會越靠左,而如果橢圓越高則越靠右。
    • Y 軸兩條格線是 10^3 (1000) 與 10^4 (10000) 格線:代表的就是那個橢圓的面積,越大則Y座標越高。

應用場景

我給幾個朋友測試過,也用這個圖表在許多場景測試過。首先每個人的狀況不一樣,所以你應該先觀察自己當下的狀態後,搭配這些圖表來進行生理回饋,了解當下的狀況。

在深呼吸或冥想或是進食時,副交感神經會像「即時煞車」一樣介入,導致心跳間期出現顯著的不規律波動。這在三張圖表中會呈現以下連動:

  1. Poincaré 圖:橢圓明顯「變胖」。點往兩側延伸,SD1 顯著增加。


  2. Autonomic State Map (ASM):即時狀態(橙色點)會向右方(SD1 軸)飄移,顯示煞車力道增強。


  3. Energy vs Balance 圖:點會往「右上角」移動,代表能量儲備(面積)增加且系統趨於平衡。



當身體進入「戰或逃(Fight-or-Flight)」模式時,交感神經會接管主導權。為了維持穩定的輸出,心臟會像精密運行的時鐘,心跳間期的變異會大幅縮小。

  1. Poincaré 圖 (Wide Mode):橢圓會變得極度「乾癟」且「細長」。所有的點緊緊貼附在 $y=x$ 的對角線上,代表 $RR_n$ 與 $RR_{n+1}$ 幾乎相等,SD1(短軸)幾乎消失。

  2. Autonomic State Map (ASM):即時狀態(橙色點)會向左方與上方移動(靠近 Y 軸),反映出 SD1(煞車)極低,而由交感主導的規律性讓點留在高位。


Energy vs Balance 圖:點會掉入「左下角」或「左方」。平衡比 $R$ 掉到 $0.5$ 以下,且因為變異性變小,總體面積(能量)也會顯著縮水。




數據本身沒有好壞,只有『適配性』。當你在深蹲或進行高強度簡報時,你應該是「壓力模式」,這代表你的身體正在全神貫注應戰。但如果是在深夜睡覺時看到這種圖,那就是身體在對你發出過度勞累的警訊了。

另外,在使用本 App 時,若看到底部指示燈變為黃色或紅色,代表手錶感測器暫時抓不到精確的脈搏波峰。這是 Garmin 光學心率感測器 (OHR) 的物理限制。你可以收緊錶帶減少雜訊、保持靜止來讓數據正常。

或在Garmin Connect IQ搜尋 POINCARÉ HRV VISUALIZER

如何在 Blogger 中呈現數學公式 (MathJax) 與程式碼高亮 (Highlight.js)

之前寫文章時,遇到公式或是程式,我向來都是隨便貼,但最近覺得好像可以用一些方式寫漂亮一點,而且現在還有 AI 協助做這件事。所以這篇文章就來看如何在 Blogger 的主題設定中加入 MathJax 與 Highlight.js,並提供完整的實作範例。

步驟一:修改主題 Header 區塊

請進入 Blogger 後台的「主題」>「編輯 HTML」,在 <head> 內加入以下設定代碼(此處已進行 HTML 轉義,確保範例不被直接執行):


<!-- MathJax 3 數學公式設定 -->
<script>
  window.MathJax = {
    tex: {
      inlineMath: [['$', '$'], ['\\(', '\\)']],
      displayMath: [['$$', '$$'], ['\\[', '\\]']],
      processEscapes: true
    }
  };
</script>
<script async='async' id='MathJax-script' src='https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js'/>

<!-- Highlight.js 程式碼高亮設定 -->
<link href='https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/a11y-dark.min.css' rel='stylesheet'/>
<script src='https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js'></script>
<script>hljs.highlightAll();</script>

<!-- 自定義 CSS 微調 -->
<style>
  pre code {
    border-radius: 8px;
    font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
    font-size: 14px;
    padding: 1.5em !important;
    line-height: 1.5;
  }
</style>

實作示範對照區

以下展示了如何在 HTML 編輯器中撰寫(原始碼)以及瀏覽器最終算出的呈現結果:

如果您想在內文顯示這段字 $a^2 + b^2 = c^2$ 而不讓它變成渲染後的公式...


<code>$a^2 + b^2 = c^2$</code>

範例 1:顯示 LaTeX 原始碼而不被公式化

如果您想在內文顯示這段字$a^2 + b^2 = c^2$,則是使用 $a^2 + b^2 = c^2$ ,若想要看到沒渲染前的樣子,則是按以下 HTML 撰寫:


<code>$a^2 + b^2 = c^2$</code>

範例 2:分段函數 (Cases)

原始 HTML 寫法:


$$|x| = \begin{cases}x & \text{if } x \geq 0 \\ -x & \text{if } x < 0\end{cases}$$

渲染呈現結果:

$$|x| = \begin{cases}x & \text{if } x \geq 0 \\ -x & \text{if } x < 0\end{cases}$$

範例 3:多行推導對齊 (Aligned)

原始 HTML 寫法:


$$\begin{aligned}(a+b)^2 &= (a+b)(a+b) \\ &= a^2 + ab + ba + b^2 \\ &= a^2 + 2ab + b^2\end{aligned}$$

渲染呈現結果:

$$\begin{aligned}(a+b)^2 &= (a+b)(a+b) \\ &= a^2 + ab + ba + b^2 \\ &= a^2 + 2ab + b^2\end{aligned}$$

範例 4:數學表格 (Array)

原始 HTML 寫法:


$$\begin{array}{|c|c|}\hline\text{角度} \theta & \sin(\theta) \\ \hline 0^\circ & 0 \\ 30^\circ & \frac{1}{2} \\ 45^\circ & \frac{\sqrt{2}}{2} \\ \hline\end{array}$$

渲染呈現結果:

$$\begin{array}{|c|c|}\hline\text{角度} \theta & \sin(\theta) \\ \hline 0^\circ & 0 \\ 30^\circ & \frac{1}{2} \\ 45^\circ & \frac{\sqrt{2}}{2} \\ \hline\end{array}$$

範例 5:Python 程式碼區塊

原始 HTML 寫法:


<pre><code class="language-python">
def hello_world():
    print("Hello Blogger!")
</code></pre>

實際高亮呈現:


def hello_world():
    print("Hello Blogger!")

總結

透過以上的「原始碼 vs 算後結果」對照,只要在 HTML 中正確使用 <pre><code> 標籤並對必要的 HTML 符號進行轉義,就能在撰寫文章時達成效果

為生殖教學設計的互動程式

 也許每年都會新增一些吧?但就先把目前有的紀錄在這。

https://chihhsiangchien.github.io/cellDivision/Index.html

細胞分裂,這個就是純粹追求視覺效果了,點細胞,細胞就會分裂成兩個。



https://chihhsiangchien.github.io/mitosis-meiosis/index.html

細胞分裂和減數分裂動畫。10多年前我曾經用flash設計過類似的,但後來無法更新。現在就是用js重新來一次,反而可以增加更多功能,像是拖曳染色體、標記同源染色體...或是可以選擇多對染色體,看它們怎麼移動。這若用我以前的作法,根本做不到啊。



https://chihhsiangchien.github.io/ChromosomeToDNA/index.html

模擬DNA到染色體的互動程式,這個印象中以前有貼過。其實就是希望把課本上的靜態圖片,變成動態立體的樣子,但目前覺得還沒有修得很好。以後想到再來改一改。



https://chihhsiangchien.github.io/3DFlower/index.html

花的構造觀察,課本上的圖太難讓學生理解了,所以就做了。實際上課的效果我覺得很好,因為比較能讓學生懂那些構造的相關性了,又有立體結構,又有花粉管的動態過程。還可以看多胚珠、單胚珠。



為恆定教學設計的互動程式

最近終於有一點時間可以來回顧上學期末的恆定課程,那時候做了什麼有趣的互動程式。

https://chihhsiangchien.github.io/breathing-mechanism/index.html

呼吸運動的演示,可強調橫膈或肋骨的運動,看看胸式和腹式呼吸的差異



https://chihhsiangchien.github.io/gas-exchange/index.html

氣體交換


https://chihhsiangchien.github.io/lung-visualization/index.html

肺臟的3D模型


https://chihhsiangchien.github.io/bird-respiration/index.html

鳥類的呼吸方式 這個就是下學期講到鳥類的氣囊時可以使用的



https://chihhsiangchien.github.io/breathing-flow/index.html

這個呼吸心流的程式,最早是因為看到IG上有很多類似的小短片,所以我就想來自己做一個。除了上課時使用之外,我平常做呼吸練習的時候也會拿來用,甚至可以搭配我所作的手錶觀察交感神經的程式來做生物回饋,看呼吸如何影響心搏。



為童軍活動設計的多種互動遊戲

雖然和童軍一點關係都沒有...

幾個月前被委託一項任務,是學校要辦理新竹市童軍大會,希望我可以幫忙出一個闖關站,讓參加的學生可以用平板完成數個遊戲。

於是我就想到什麼點子,就來寫一個程式。寫了一大堆,但後來在闖關時其實只用了一個啊。

來看看做了哪些遊戲可以玩。


https://chihhsiangchien.github.io/taiwan-map-puzzle/index.html

台灣地圖拼圖:看縣市的輪廓做選擇題



https://chihhsiangchien.github.io/number-idiom-game/index.html

數字成語遊戲 就是填上成語的數字



https://chihhsiangchien.github.io/hidden-symbol-game/index.html
運算符號 


https://chihhsiangchien.github.io/flag-quiz/index.html

國旗大師

https://chihhsiangchien.github.io/color-perception-game/index.html

色彩辨識


https://chihhsiangchien.github.io/concentration-grid/index.html

舒爾特方格專注力挑戰



https://chihhsiangchien.github.io/color-illusion/index.html

色彩錯覺


所有遊戲都在這個入口可以找到,當然這還有我用在教學上的互動程式,有興趣再看看

https://chihhsiangchien.github.io/

為 Garmin 手錶開發即時心率變化圖表Real-time Heart Rate Graph

幾年前,我曾開發過一個在平板上透過鏡頭觀測脈搏變化的 Web 程式。當時就在想:如果能在運動手錶上即時看到這種動態曲線,該有多好?

對於搭載 PPG 光學心率感測器的 Garmin 手錶來說,這技術上絕對可行,但原生系統卻缺乏這種直觀的即時圖表。於是我決定自己動手開發。

開發之路:與 Monkey C 的初次邂逅

我從下載 Garmin SDK 開始,並在 VS Code 安裝了 Monkey C 延伸套件。雖然是一門相對冷門的語言,但翻完官方文件後,我決定從最精簡的「Data Field」(資料欄位)切入。

經過幾天的奮戰,這個HRGraph 誕生了:

  • 核心邏輯:在 HRGraphView.mc 中利用 Array<Number> 滾動儲存歷史數據。

  • 繪製技術:透過 dc.drawLinedc.fillCircle 手繪向量圖形,確保畫面流暢。

  • 數據採集:利用 compute 函式,每秒精準抓取一次 Activity.Info 中的心率數值。

  • 支援動態縮放 Y 軸

實戰應用:硬舉與冥想的生物回饋

這不只是一個圖表,它是我觀察生理狀態的視窗,例如在硬舉結束後的幾秒內,我觀察到心率會先短暫下降後隨即攀升,約數十秒後才開始回落;這時我會透過特定呼吸法誘發副交感神經,並從圖表即時觀察心率的變化。在休息冥想時,它更是我練習「諧振呼吸 (Resonant Frequency Breathing)」的最佳助手。

這款 App 已正式上架至 Garmin Connect IQ,如果你是 Garmin 用戶,可以直接在 Connect IQ 搜尋Real-time Heart Rate Graph下載使用!

下載安裝後,若你在 App 清單中找不到它請別擔心,因為這是一個 「資料欄位 (Data Field)」,而非獨立 App。你需要將它加入到特定運動的數據頁面中,設定步驟如下:

  1. 選擇運動模式:在手錶上按下 Action 鍵(右上),選擇一項活動(如:跑步、步行或重訓)。

  2. 進入活動設定:在開始運動前,向上滑動螢幕或長按 Menu 鍵(右下)進入設定選單。

  3. 編輯數據畫面:依序進入 「[活動名稱] 設定」 > 「數據畫面」

  4. 自訂欄位:選擇一個您想修改的頁面,並確保該頁面的佈局(Layout)包含可放入數據欄位的區塊。

  5. 選取 ConnectIQ 插件:點擊您想更換的欄位,在類別中找到 「ConnectIQ」,並選取 「Real-time Heart Rate Graph」

  6. 開始體驗:設定完成後,只要開啟該運動模式,動態心率圖表就會即時出現在手錶螢幕上!


source code https://github.com/ChihHsiangChien/GarminProjects/tree/main/HRGraph


用 AI 讓 10 年前的小型電腦切割機在 Linux 重生

許多年前在網路上購入了一台「理鋒科技小型電腦切割機」,當時環境還是在 Windows 下,廠商隨附了專用的驅動與操作軟體。但隨著我的工作環境全面轉向 Linux,這台機器便因為軟體不相容而長期閒置。

最近因為臨時有割字需求,我開始研究如何在 Linux 下驅動這台老機器。

從 Inkcut 到硬體限制
起初我找到了開源專案Inkcut,它確實能讀入 SVG 並執行切割。但實際操作後發現一個致命問題:硬體緩衝區(Buffer)太小。當圖形較複雜時,硬體接收指令的速度跟不上軟體送出的速度,導致傳輸溢位,切割中途就會直接罷工。

轉向底層:直接與硬體通訊
既然 Inkcut 是將指令轉換為 HPGL 格式送出,我想:不如直接撰寫符合我硬體特性的指令發送器?

透過 lsusb 指令,我確認了機器連接的晶片資訊: 
Bus 003 Device 052: ID 4348:5584 WinChipHead CH34x printer adapter cable

協同開發:與 Gemini 定義 AI Agent 規格書
我釐清了需求:需要一個視覺化的程式,能讀取 Inkscape 製作的 SVG,並具備「節流(Throttling)」功能。我透過與 Gemini 的不斷對談,最終整理出一份給 AI Agent 執行的專案規格書(Spec):

AI Agent 專案規格書:Linux 智慧切割傳送器 (PyCutter)

1. 專案目標 (Project Objective)

開發一個輕量化的 Linux 桌面應用程式,能夠讀取 SVG 檔案,自動轉換為 HPGL 指令,並透過 序列埠 (Serial Port) 實時監控並「節流傳送」給割字機,解決硬體緩衝區溢位導致的停機問題。


2. 技術棧要求 (Tech Stack)

  • 語言: Python 3.10+

  • GUI 框架: Tkinter (內建,確保輕量) 或 PyQt6

  • 向量處理: svgpathtools (解析 SVG)

  • 硬體通訊: pyserial (串列埠通訊)

  • 多執行緒: threading (分離 UI 與傳送邏輯)


3. 核心功能模組 (Functional Modules)

A. 檔案與路徑處理 (SVG to HPGL)

  • 自動解析: 將 SVG 中的 <path>, <rect>, <circle> 轉換為點座標。

  • 動態離散化 (Adaptive Sampling): * 直線僅保留端點。

    • 曲線根據半徑自動決定點數,確保點間距落在 0.5mm ~ 1.0mm

  • 單位換算: 固定比例 $1\text{ mm} = 40\text{ plotter units}$ ($1016\text{ DPI}$)。

  • 路徑優化: 實作簡單的「最近鄰算法」,減少抬刀移動的空跑距離。

B. 硬體連線管理 (Connection Manager)

  • 自動偵測: 啟動時掃描 /dev/ttyUSB*/dev/ttyACM*

  • 連線選單: 下拉式選單切換 Port,支援重新整理。

  • 通訊設定: 預設 9600 鮑率,支援 RTS/CTS 硬體流控 設定。

C. 視覺化監控畫布 (Live Monitor)

  • 同步繪圖: 當指令送出時,畫布上的游標同步移動並繪製線條。

  • 座標縮放: 自動將 HPGL 萬級座標縮放至 600x400 的視窗內。

  • 顏色區分: 抬刀 (PU) 用灰色虛線,下刀 (PD) 用紅色實線。

D. 流量控制傳送引擎 (Throttled Sender)

  • 逐行傳送: 讀取 HPGL 陣列,一條一條 ser.write()

  • 智慧延遲: 提供滑桿調整 Inter-step Delay (預設 0.05s)。

  • 暫停/終止: 支援切割中途立即停止通訊。


4. 使用者介面需求 (UI Requirements)

  • 頂部列: Port 選擇、連接按鈕、重新整理按鈕。

  • 左側邊欄: * 速度延遲設定 (Slider)。

    • 曲線平滑度設定 (Slider)。

    • 檔案選擇按鈕。

  • 中央區域: 繪圖監控大畫布。

  • 底部狀態列: 顯示 當前指令/總指令數預計剩餘時間連線狀態


5. AI Agent 的開發任務分解 (Task Breakdown)

你可以要求 Agent 按照以下順序執行:

  1. Task 1: 編寫一個獨立的 Scanner 類別,列出 Linux 所有序列埠。

  2. Task 2: 使用 svgpathtools 編寫轉換邏輯,輸入 SVG 路徑,輸出符合 1016 DPI 的 HPGL 字串列表。

  3. Task 3: 建立 Tkinter 主視窗,包含 Canvas 繪圖與基本的 UI 佈局。

  4. Task 4: 整合 pyserial 傳送引擎,確保在 threading 模式下運作。

  5. Task 5: 優化:加入 stty 權限檢查邏輯,若無權限則彈窗提示。


6. 預期錯誤處理 (Error Handling)

  • Permission Denied: 提示使用者執行 sudo usermod -aG dialout $USER

  • Disconnected: 若傳送中 USB 拔除,應立即捕捉異常並停止計時。

  • Scale Error: 若圖檔太大超過畫布,應具備 Fit to Canvas 的自動縮放邏輯。

補充規格:原點校正與座標偏置 (Origin & Offset Calibration)

1. 核心邏輯 (The Logic)

割字機通常有兩個原點概念:

  • Machine Home (機械原點): 機器開機後感應器觸碰到的物理極限點。

  • User Origin (用戶原點/工作起點): 你手動將刀頭移動到材料邊角後設定的 (0,0)

2. 功能需求 (Feature Requirements)

AI Agent 需要在 UI 上增加一個「校正面板」,包含以下功能:

  • Jog Control (手動微調): * 提供上下左右按鈕,發送微小的移動指令(例如每次移動 1mm 或 10mm)。

    • 指令範例:PU{current_x + 40},{current_y};

  • Set As Origin (設為原點): * 當用戶把刀移到滿意的位置後,按下此鍵。

    • 軟體會將當前的機械座標記錄為 Offset_XOffset_Y

    • 之後所有的 SVG 座標都會加上這個 Offset。公式:Final_X = (SVG_X * 40) + Offset_X

  • Go To Origin (回起點): * 快速測試指令:PU0,0; (相對於用戶原點)。

3. 視覺化配合

  • 在畫布(Canvas)上,用一個「十字準星」或「紅色方框」標示出目前刀頭的預期位置。

  • 當用戶點擊微調時,畫布上的準星要同步移動。

 


我將這份 Spec 投入 Gemini CLI,讓它進行實作。經過幾輪的 Debug 與邏輯修正,我的切割機終於在 Linux 上完美復活了!

如果你也有類似的老舊割字機在 Linux 下無法使用的困擾,歡迎參考或 Fork 我的專案,並利用 AI Agent 針對你的硬體特性進行微調。



久違了,雷文霍克顯微鏡的製作

真的久違了,上次把我的單式顯微鏡材料箱打開來,已經是好幾年前的事了。

這次重啟是因為輔導團的增能活動,我規劃了這個活動,以及用這個活動做AI相關的課程。

以往我做這個活動,其實有一個不確定性,因為過去總說雷文霍克這樣製作顯微鏡,但其實科學家們並不確定, 因為鏡片被緊緊鑲嵌在兩片黃銅板之間,孔徑極小,科學家過去無法在不破壞文物的情況下觀察鏡片的完整形態所以,大家只能猜測他的做法。不過2021年《科學進展》(Science Advances)的科學研究刊出了這篇文章,秘密揭曉!

Neutron tomography of Van Leeuwenhoek’s microscopes〈雷文霍克顯微鏡的中子斷層掃描〉


研究團隊決定使用中子輻射來進行非侵入式掃描,因為中子能輕易穿透金屬,並對玻璃產生良好的對比度,從而重建出精確的 3D 模型。

研究人員掃描了兩台保存在荷蘭的原始顯微鏡(一台放大倍率 118x,另一台則是現存最強的 266x),結果出人意料:

中倍率顯微鏡(118x): 證實使用了傳統的研磨與拋光技術,鏡片形狀如同一顆對稱的扁平豆子(透鏡狀)。但高倍率顯微鏡(266x): 內部竟然是一顆球形鏡片(Ball-shaped lens),3D 影像清楚顯示球形鏡片上連接著一根細小的玻璃梗(Stem)。


 這種形狀完全符合虎克在 1678 年發表的「簡易熔融法」,是將玻璃絲末端加熱熔化成球狀。就是過去我帶課程時使用的方法,也就是說我過去說雷文霍克用這樣做顯微鏡鏡片,這是可以確認的。

這件事實的發現,有很重大的意義,因為這種製作鏡片的方式在虎克的Micrographia這本書裡早有記載。

"...take a very clear piece of a broken Venice Glass, and in a Lamp draw it out into very small hairs or threads, then holding the ends of these threads in the flame, till they melt and run into a small round Globul, or drop, which will hang at the end of the thread; and if further you stick several of these upon the end of a stick with a little sealing Wax, so as that the threads stand upwards, and then on a Whetstone first grind off a good part of them, and afterward on a smooth Metal plate, with a little Tripoly, rub them till they come to be very smooth..."

「……取一塊非常清澈的威尼斯碎玻璃,在燈火上將其拉成極細的絲線,接著將這些玻璃絲的末端置於火焰中,直到它們熔化並匯聚成一個懸掛在絲線末端的小圓球或小滴。

若進一步用少許密封蠟將其中幾顆固定在木棒末端,使玻璃絲朝上,接著先在磨刀石上磨掉大部分(球體),最後在光滑的金屬板上沾一點矽藻土(Tripoly)進行摩擦,直到它們變得非常平滑……」

 

虎克在 1665 年時建議將球體磨掉一部分以形成「平凸透鏡」,因為他當時擔心多重折射會導致影像模糊。然而,論文研究發現,雷文霍克顯然發現了直接使用完整球體(連帶著那根細小的玻璃梗作為把手)其實效果極佳,只要控制好光圈即可。

雷文霍克生前曾公開批評虎克的熔融法會產生雜質,宣稱自己不使用球形鏡片。但掃描結果證明他其實私下採用了虎克的技術,並將其改良至完美的境界。關鍵在於他對孔徑控制與鏡片安裝的極致追求,將當時已知的技術發揮到了物理極限。


看到這些敘述之後,就讓我想要重新修改材料來試一次。先說結論,以下的做法確實可以得到高品質的影像,比起我之前的作法好很多。

材料改用厚紙板,並使用3mm的打孔器打孔


 

接著用鋁箔膠帶,先將孔貼住




再使用縫衣針在圓孔正中央,將鋁箔膠帶穿一個小洞,此步驟務必直上直下,使洞口在正中

接著再把燒融的玻璃球或購置的玻璃球放入,用鋁箔膠帶的黏性把鏡片黏著就可以了。

這樣就做完全部的事情了。

這和以往我的材料有幾個不同

1.以前用卡紙,現在用厚紙板更厚實不變形

2.用鋁箔膠帶取代以前用卡點西德,更容易獲得完美的光圈


而光源的部分則是採用自製的LED光源




玻片標本的製作建議使用軟塑膠片,貼上標本(如洋蔥表皮)後貼上透明膠帶就可以。這樣製作的好處是你可以彎曲塑膠片為微調標本的位置,使標本落在鏡片的焦點。



ADS-B 數位觀察:分析桃園空域航線、高度與信號傳播特性

 時隔兩週,其實我的這項用RTL-SDR V4收飛機訊號的計畫還在持續進行,甚至我還把收到的訊號轉送去Flightradar24flightaware,當然也獲得了一些很棒的會員權益,基本上就是可以取用資料庫。對這有興趣的話,兩者的官網都有資訊,但前提是你用的是linux的機器才好操作。(像我就是在樹莓派上運行)

從飛機收來的訊號不單是只有位置訊號,而是有這麼多不同的資料,但並不是每一台飛機都的資料內容都有這些,總之我是都即時用我房間的小天線,能抓都抓了。

參數 (Parameter)英文全稱 (English Full Name)中文說明 (Chinese Description)
$tTimestamp時間戳記 (接收訊號的時間)
.hexICAO Hex IDICAO 24位元位址碼 (飛機唯一識別碼)
.flightFlight Number / Callsign航班編號 / 呼號
.latLatitude緯度
.lonLongitude經度
.alt_baroBarometric Altitude氣壓高度
.alt_geomGeometric Altitude幾何高度 (GPS 高度)
.gsGround Speed地速
.iasIndicated Airspeed指示空速
.tasTrue Airspeed真空速
.machMach Number馬赫數
.trackTrack Angle航跡角
.track_rateTrack Rate航跡變化率 (轉彎率)
.rollRoll Angle滾轉角 (飛機坡度)
.mag_headingMagnetic Heading磁航向
.true_headingTrue Heading真航向
.baro_rateBarometric Vertical Rate氣壓爬升/下降率
.geom_rateGeometric Vertical Rate幾何爬升/下降率
.tempStatic Air Temperature靜溫 (外界環境溫度)
.wdWind Direction風向
.wsWind Speed風速
.nav_qnhQNH Setting氣壓高度表修正值 (海平面氣壓)
.nav_altitude_mcpMCP Selected Altitude自動駕駛設定高度 (MCP/FCU)
.selected_headingSelected Heading設定航向
.squawkSquawk Code應答機代碼 (四位數識別碼)
.rssiReceived Signal Strength Indication接收訊號強度指標
.messagesMessage Count訊息總數
.rcRadius of Containment容納半徑 (位置完整性)
.nic_baroNavigation Integrity Category (Baro)氣壓高度完整性類別
.nac_pNavigation Accuracy Category (Position)位置精確度類別
.nac_vNavigation Accuracy Category (Velocity)速度精確度類別
.silSource Integrity Level源完整性等級
.gvaGeometric Vertical Accuracy幾何垂直精確度
.sdaSystem Design Assurance系統設計保證
.categoryAircraft Category飛機類別 (重量或類型)
.nav_modesNavigation Modes導航模式狀態
.versionADS-B VersionADS-B 版本

我用one-liner列出目前收到的每日航班量,命令是

 printf "%-15s | %-10s\n" "Date (File)" "Flight Count" && \
printf "%-15s-|-%-10s\n" "---------------" "----------" && \
for f in adsb_*.csv; do 
    count=$(cut -d',' -f3 "$f" | grep -v "N/A" | sort -u | wc -l);
    printf "%-15s | %-10s\n" "$f" "$count"; 
done

Date (File)     | Flight Count
----------------|-----------
adsb_2026-02-17.csv | 693       
adsb_2026-02-18.csv | 1140      
adsb_2026-02-19.csv | 1114      
adsb_2026-02-20.csv | 1019      
adsb_2026-02-21.csv | 992       
adsb_2026-02-22.csv | 1030      
adsb_2026-02-23.csv | 963       
adsb_2026-02-24.csv | 971       
adsb_2026-02-25.csv | 921       
adsb_2026-02-26.csv | 892       
adsb_2026-02-27.csv | 1016      
adsb_2026-02-28.csv | 937       
adsb_2026-03-01.csv | 531       
adsb_2026-03-15.csv | 29      

中間有幾天電腦重啟,忘記繼續開紀錄的sh,就沒紀錄了,但是可以看到每天都能收到數百筆航班資料。

既然有大量資料,當然就是做我最愛的資訊視覺化,以下都用R來畫某一天的圖。
這用的是飛機的類型來繪圖,有小飛機、大飛機...。



每日收到的訊號點的圖



依照航向和高度來區分,黃線就是小於2000ft的,大部分都是起飛和降落的,可以觀察桃園機場一帶在這段時間是如何進場的。因為看到這張圖,我還因此跑去永安北岸觀海亭,想要實際看從北方來的飛機轉進桃園機場的即時現況。藍綠色線的是往北的高空飛機,紫色則是往南的。


看到這些線,就一定要去看看「交通部民用航空局電子式飛航指南」去找TAIPEI FIR EN ROUTE CHART來對照,才會更好玩。下圖的進入路徑是這樣

  1. 你要先到飛航指南 https://ais.caa.gov.tw/aimportalapp/ 去底下按eAIP 看最新的資料
  2. 到- Part 2 - 航路 (ENR) 找   ENR 6 航路圖,裡面有很多圖,看到TAIPEI FIR EN ROUTE CHART 點下去就是了

左右對照看到什麼,是不是就看最明顯三條是A1/W4、B1、M750,另外還有淺淺的B591、W8。看著看著想起以前我拍攝天空的縮時攝影時,就透過飛機雲的樣子推測過我家附近至少會看到三條航線,現在透過ADS-B的資料來對照,果然沒錯。




如果根據每分鐘改變的高度,來分類是爬升還是下降,紅色是下降,綠色是上升,當然在國際航線上,就是一直維持高度不變的灰色,而綠色的就對應了桃園機場和松山機場起飛的飛機囉。




再來我們看看信號的強弱和空間的關係。某些地區幾乎沒有訊號,大多是因為飛機本來就不會飛到那。



如果再把訊號與接收端的距離和高度做強度的比較,這樣就能看到經典的自由空間路徑損耗(Free Space Path Loss, FSPL)特性。近距離 (0-25km): 訊號強度(RSSI)從 -20 dBFS 快速掉落到 -30 dBFS。這是因為在距離很近時,距離增加一倍對訊號的百分比影響很大。遠距離 (50km 以上): 曲線開始趨於平緩,衰減的速度隨著距離增加而放慢。

高度 (Altitude) 的分層效應,觀察顏色的分佈:紫色點(低高度): 集中在左側(近距離)。因為飛機在低高度時,受限於地球曲率和地形遮蔽(障礙物),只能收得到近處的訊號。黃綠色點(高度 30000+ ft): 這些點可以延伸到 100km 甚至更遠。這是因為高空飛機與接收器之間通常沒有建築物或山脈阻擋,訊號路徑更接近純粹的物理模型。


除了這些raw data畫出的數據,其實透過這些數據的組合還有更多有趣的事,等下篇再來分享。