粉紅渦蟲養殖場

國中生物會教到兩次渦蟲,一次在無性生殖,第二次在扁形動物。課堂上播放的影片總是讓學生一提到渦蟲就直呼「好可愛!」、紛紛說著想把牠「切一切」,甚至還有幾個學生興致勃勃地說好想帶回家養。

聽學生說了幾次之後,我就隨口應了一句:「那我弄個數位板的渦蟲給你們養吧!」

就像過去開發教學工具一樣,先在腦海中想好具體目標,接著透過 AI Agent 協作,這款療癒系的「數位渦蟲養殖場」就這麼來了!

線上連結:
https://chihhsiangchien.github.io/pinky-planarian/

數位渦蟲的技術與特色

這款養殖模擬器利用了物理模擬技術,呈現出渦蟲那種軟綿綿、四處游動的動態。在功能設計上可以進行餵食,觀察牠們長大,還可以切斷再生。

網頁支援 PWA (Progressive Web App) 技術。使用者可以像安裝手機 App 一樣,直接將這個網頁添加到手機的主畫面,隨時隨地開啟這個口袋渦蟲水族箱。

互動工具箱功能

系統的互動工具如下:

  • 觀察: 好奇的渦蟲會受到吸引,主動游向你的滑鼠指標並盯著它看。
  • 餵食: 投餵愛心形狀的食物,點擊後看著渦蟲慢慢將其吸收並逐漸長大。
  • 魔法棒: 揮舞魔法棒切割渦蟲,牠們就會在畫面上即時分裂並展現再生能力。
  • 逗弄: 游標直接接觸渦蟲時,可以真實地撥弄、干擾牠們的游動方向。
  • 召喚: 點擊畫面任意空白處,可以發出訊號吸引全場的渦蟲聚集。
  • 換水: 當背景逐漸變綠、代表水質惡化,渦蟲的活動力會隨之下降。此時必須趕快換水,否則渦蟲會開始萎縮。
  • 療癒音訊: 背景搭配了即時合成的 3/4 拍音樂盒 BGM,並針對各項互動加入了清脆的特效音。

用電腦顯示MLX90640熱像攝影模組的熱像

幾年前買了一個熱影像攝影模組/紅外線陣列 MLX90640(110度版本),當時搭配 Wio Terminal 一起購買,主要是將影像直接放在 Wio Terminal 的小螢幕中顯示。那時的程式碼直接套用廠商提供的範例,也沒有深入去管它的運作原理。

最近重新翻出這個有趣的機器,心裡冒出一個新目標:想要把熱影像直接在電腦螢幕上呈現。畢竟在教學現場或實驗演示時,這樣才方便直接投影到大螢幕上讓大家看清楚。稍微研究了一下它的規格,原來 MLX90640 的通訊方式是 $I^2C$。那基本邏輯就很簡單了:我只要用一個開發板負責透過 $I^2C$ 把 MLX90640 偵測到的溫度數據拿回來,再透過 UART 序列通訊(Serial)轉送進電腦,最後電腦端用 Python 來處理這些數據並即時繪圖就可以了!

架構:
MLX90640 (感測器) → [I2C] → Wio Terminal (轉運站) → [USB Serial] → 電腦 → [Python + OpenCV] → 大螢幕投影

既然原本就是搭配 Wio Terminal,那我就直接用它來當作數據的「轉運站」。以下是處理序列傳輸的 Arduino 端的程式碼:

Wio Terminal 端:序列轉運站程式碼

#include <Adafruit_MLX90640.h>

Adafruit_MLX90640 mlx;
float frame[32*24]; 

void setup() {
  Serial.begin(115200); 
  while (!Serial) delay(10); 

  Wire.begin();
  // 如果 1MHz 會卡死,先退回標準的快速模式 400kHz,這最安全穩定
  Wire.setClock(400000); 

  if (!mlx.begin(MLX90640_I2CADDR_DEFAULT, &Wire)) {
    while (1) delay(10); 
  }

  mlx.setMode(MLX90640_CHESS); 
  
  // 將解析度降為 16 位元(或 MLX90640_ADC_17BIT)
  mlx.setResolution(MLX90640_ADC_16BIT); 

  mlx.setRefreshRate(MLX90640_4_HZ); // 可以先從 2Hz/4Hz 驗證,再嘗試調成 MLX90640_8_HZ
}

void loop() {
  // 抓取一影格的溫度資料 (這一步在高頻時會阻塞等待感測器準備好)
  if (mlx.getFrame(frame) != 0) {
    return; 
  }

  // 將 768 (32x24) 個點的數據輸出到電腦
  for (int i = 0; i < 768; i++) {
    // 小數點改為 1 位即可(例如 26.5),減少一半的 Serial 傳輸字元量,讓速度更快
    Serial.print(frame[i], 1); 
    if (i < 767) {
      Serial.print(",");
    }
  }
  Serial.println(); 
}

接著是電腦端的重頭戲。我們利用 Python 讀取 Serial 連接埠的資料,將收到的 768 個浮點數重新組裝成 $32 \times 24$ 的矩陣。為了讓視覺效果更好,程式中使用了雙三次插值(Bicubic Interpolation)將解析度平滑放大,並套用內建的 COLORMAP_TURBO ,加上了滑鼠移動即時顯示該點「真實溫度」的功能!

電腦端:Python 數據接收與熱影像視覺化

import serial
import serial.tools.list_ports
import numpy as np
import cv2
import time

BAUD_RATE = 115200
SCALE_FACTOR = 20     # 32x24 放大 20 倍 = 640x480

# 全域變數,用來記錄滑鼠當前在放大影像上的 (x, y) 座標
mouse_x = -1
mouse_y = -1

def mouse_move_callback(event, x, y, flags, param):
    """ 監聽滑鼠移動事件 """
    global mouse_x, mouse_y
    if event == cv2.EVENT_MOUSEMOVE:
        mouse_x = x
        mouse_y = y

def find_wio_port():
    """ 自動尋找或讓使用者手動選取序列埠 """
    ports = list(serial.tools.list_ports.comports())
    if not ports:
        print(" 錯誤:電腦目前沒有連接任何序列埠裝置(請檢查 USB 線)。")
        return None

    for p in ports:
        description = p.description.lower()
        manufacturer = (p.manufacturer or "").lower()
        if "seeed" in description or "arduino" in description or "seeed" in manufacturer:
            print(f" 自動偵測到 Wio Terminal: {p.device}")
            return p.device
            
    if len(ports) == 1:
        print(f" 自動選定唯一的序列埠: {ports[0].device}")
        return ports[0].device
        
    print("\n 找到多個序列埠,請手動選擇 Wio Terminal 連接的編號:")
    for i, p in enumerate(ports):
        print(f"  [{i}] {p.device} - {p.description}")
        
    while True:
        try:
            choice = input(f"\n請輸入編號 (0-{len(ports)-1}): ").strip()
            idx = int(choice)
            if 0 <= idx < len(ports):
                return ports[idx].device
            else:
                print(f"請輸入 0 到 {len(ports)-1} 之間的數字。")
        except ValueError:
            print("請輸入有效的數字。")

def main():
    global mouse_x, mouse_y
    
    PORT = find_wio_port()
    if not PORT:
        return

    try:
        ser = serial.Serial(PORT, BAUD_RATE, timeout=1)
        print(f" 成功連線至: {PORT}")
    except Exception as e:
        print(f" 錯誤:無法開啟序列埠 {PORT}。原因: {e}")
        return

    time.sleep(1.5)
    ser.reset_input_buffer()

    # 先建立視窗,這樣才能綁定滑鼠事件
    window_name = "Wio Terminal MLX90640 Smooth Thermal"
    cv2.namedWindow(window_name)
    cv2.setMouseCallback(window_name, mouse_move_callback)

    print("\n 影像視窗已啟動!")
    print(" 提示:將滑鼠移入畫面即可顯示當前位置溫度。點擊影像視窗並按 'q' 鍵可關閉程式。")

    while True:
        if ser.in_waiting > 0:
            try:
                line = ser.readline().decode('utf-8').strip()
                if not line:
                    continue
                
                str_list = line.split(',')
                if len(str_list) != 768:
                    continue
                
                # 重組原始 24x32 的溫度矩陣
                raw_matrix = np.array(str_list, dtype=np.float32).reshape(24, 32)
                raw_matrix = np.fliplr(raw_matrix)  # 鏡像修正(讓畫面與手同向)

                min_temp = np.min(raw_matrix)
                max_temp = np.max(raw_matrix)
                
                # 正規化到 0~255 灰階
                if max_temp == min_temp:
                    gray_img = np.zeros((24, 32), dtype=np.uint8)
                else:
                    gray_img = (255 * (raw_matrix - min_temp) / (max_temp - min_temp)).astype(np.uint8)
                
                # 雙三次插值美化放大
                target_size = (32 * SCALE_FACTOR, 24 * SCALE_FACTOR)
                smooth_img = cv2.resize(gray_img, target_size, interpolation=cv2.INTER_CUBIC)
                
                # Turbo 高質感色圖
                color_heatmap = cv2.applyColorMap(smooth_img, cv2.COLORMAP_TURBO)
                
                # --- 核心:計算滑鼠所在位置的溫度 ---
                if 0 <= mouse_x < target_size[0] and 0 <= mouse_y < target_size[1]:
                    # 將放大後的像素座標 (mouse_x, mouse_y) 反推回原始的 32x24 矩陣索引
                    orig_col = int(mouse_x / SCALE_FACTOR)
                    orig_row = int(mouse_y / SCALE_FACTOR)
                    
                    # 邊界安全檢查
                    orig_col = max(0, min(orig_col, 31))
                    orig_row = max(0, min(orig_row, 23))
                    
                    # 抓取未經插值的「真實溫度」
                    current_pixel_temp = raw_matrix[orig_row, orig_col]
                    
                    # 在滑鼠游標右下方繪製十字與溫度標籤
                    text_x = mouse_x + 15
                    text_y = mouse_y + 15
                    
                    # 防止文字超出右方與下方邊界
                    if text_x > target_size[0] - 120: text_x = mouse_x - 110
                    if text_y > target_size[1] - 10: text_y = mouse_y - 15
                    
                    # 畫一個小小的中心點十字提示
                    cv2.drawMarker(color_heatmap, (mouse_x, mouse_y), (255, 255, 255), 
                                   markerType=cv2.MARKER_CROSS, markerSize=10, thickness=1, line_type=cv2.LINE_AA)
                    
                    # 秀出滑鼠指著的溫度(黑框白字看得比較清楚)
                    cv2.putText(color_heatmap, f"{current_pixel_temp:.1f} C", (text_x, text_y), 
                                cv2.FONT_HERSHEY_DUPLEX, 0.5, (0, 0, 0), 3, cv2.LINE_AA)
                    cv2.putText(color_heatmap, f"{current_pixel_temp:.1f} C", (text_x, text_y), 
                                cv2.FONT_HERSHEY_DUPLEX, 0.5, (255, 255, 255), 1, cv2.LINE_AA)
                
                # 固定顯示左上角的最高/最低溫
                cv2.putText(color_heatmap, f"Max: {max_temp:.1f} C", (15, 40), 
                            cv2.FONT_HERSHEY_DUPLEX, 0.6, (255, 255, 255), 1, cv2.LINE_AA)
                cv2.putText(color_heatmap, f"Min: {min_temp:.1f} C", (15, 70), 
                            cv2.FONT_HERSHEY_DUPLEX, 0.6, (255, 255, 255), 1, cv2.LINE_AA)
                
                cv2.imshow(window_name, color_heatmap)

            except Exception as e:
                pass
                
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    ser.close()
    cv2.destroyAllWindows()
    print("程式已安全結束。")

if __name__ == "__main__":
    main()

這樣一來,原本在 2.4 吋小螢幕裡的熱影像,就能順利以 640x480 的畫面投影到教室的大螢幕上了!就能拿來做生物體溫、植物蒸散作用或環境物理性質的演示。




無線傳電的Demo教具

上週研習時,學校的君翰老師帶大家做了電磁學的教具,原型是其他老師去科工館看到的。原本的設計是透過繞線圈,觀察通電後的磁場改變如何使得指北針偏轉。

四周有兩處缺刻的壓克力板,對於固定繞線圈十分友善。看著繞好的線圈,我突然有了靈感,好像可以把它改造成其他教具?於是,我跟同事要了一套他的材料,拿回來摸索一下,最後成功做出了一個「無線傳電教具」。

無線傳電教具成果展示

先來看看實際的展示效果。下圖左邊是發射線圈(加了一些電子元件,後面會詳細說明),右邊則是接收線圈,結構非常純粹:只有漆包線圈直接接上一顆紅光 LED 燈。

實驗操作時,完全不需要任何實體導線連接,只要將接收線圈逐漸靠近發射線圈,右側的紅光 LED 就會神奇地亮起來!

動態演示影片如下:

自製電路佈線與製作細節

這個無線傳電教具的核心在於發射端的「自激震盪電路」。電路的繞線與接線方式如下:

  • 發射線圈繞法:先繞 10 圈之後,留出一段漆包線不繞(作為中央抽頭),隨後順著相同方向繼續繞 10 圈。這個中央抽頭要連接到 3V 電池的正極。(註:也可以用兩組獨立的 10 圈線圈,將其中一組的尾端與另一組的頭端相接,同樣能拉出中央抽頭)。
  • 電晶體接線:整組發射線圈的「頭端」和「尾端」,分別連結到三極體(電晶體)的 C極(集極)B極(基極)
  • 安全保護:在連接 B極 之前,必須先串聯一個電阻,避免流入基極的電流過強而燒毀元件。
  • 電源迴路:電晶體的 E極(射極) 則直接接回電池的負極(GND)。
  • 接收線圈:同樣繞 10 圈並直接閉路接上紅光 LED。依據實際測試,接收端理論上可以用更少的圈數達到同樣的感應效果。

科學原理

自激震盪與電磁感應:
這個電路的核心目的,是利用電晶體達成極高頻率的「導通與斷電」切換。通電瞬間,連接 B極 的線圈導電並產生初始磁場,此磁場的變化使連接 C極 的線圈產生感應電動勢,進而誘發更大的電流。這個增強的電流反過來進一步推動 B極,使電晶體迅速達到「飽和導通」。當電晶體飽和後,電流不再變化,磁場隨之停止增長;失去了變化的磁場,B極 失去推動力,電晶體隨即斷電。斷電後整個過程再度重演。

透過上述的循環,發射線圈便能產生高頻率的電磁震盪。此時,一旁的接收線圈切換進入這個快速變化的磁場中,根據法拉第電磁感應定律,接收線圈內就會產生感應電流,成功推動紅光 LED 發光。

原型實驗與替代材料建議

除了使用研習提供的壓克力板作為繞線骨架外,其實我一開始進行概念驗證時,是直接拿市售整捆未拆的漆包線圈來做發射端的。當時先把線圈拆開重新繞製,抓出中央抽頭;而接收端則是隨手繞在普通的紙杯或塑膠杯上。這種作法,也適合作為學生探究與實作的課堂材料,在此一併分享給大家參考。


科學魔法車的兩級 RC 弛張震盪器 (RC Relaxation Oscillator)

 我在學校有開一個給資優生的選修課,用的是科學魔法車,是教學生電子電路應用和寫程式。


在這個課程中,用的是曹老師設計的教材,其中有一個電路很有趣,是我會多花一點時間給學生玩的,就是這個兩級 RC 弛張震盪器 (RC Relaxation Oscillator)




用的就是一顆反向器IC 4069,藉由四個反向器搭建出來的高頻與低頻振盪電路,確定會發出聲音之後,下一步就可以把30 KΩ 和 20 KΩ 換成可變電阻,然後就可以任意用這兩個電阻創造不同的聲音。

最後再把可變電阻換掉,變成兩條鱷魚夾。然後準備一張紙,紙上用2B鉛筆畫出筆跡,延伸到紙緣,用其中一條鱷魚夾夾住。另外一個鱷魚夾就可以在筆跡上面移動,就像是可變電阻那樣控制出不同的聲音。

如果設計得當,甚至是可以創造出音階,好好唱一首歌曲喔。




用micro:bit玩體感遊戲

前一陣子都在玩 micro:bit,嘗試用它來做各項應用。回憶起很早以前玩 Scratch 時,除了在電腦上直接按鍵控制角色外,也可以透過一些軟硬體套件配合,利用 Arduino 來連動。後來也有軟體搭配,讓 micro:bit 能夠控制 Scratch 角色。這讓我想到:我應該可以繞過這些複雜的套件,直接用 micro:bit 來控制網頁遊戲吧?

技術原理與可行性

結合以下三個核心特色,就能達成硬體與網頁遊戲的互動:

  • 感測器豐富:micro:bit 內建傾斜、加速度和光線感測器,能即時產生動態數據。
  • 多元傳輸介面:數據可以透過 UART Serial(USB 線)或藍牙(Bluetooth)傳回電腦。
  • 現代瀏覽器支援:目前的瀏覽器(如 Chrome)已支援 Web BluetoothWeb Serial API,可直接讀取硬體資料。

micro:bit 體感遊戲控制專案

基於上述構想,我製作了一個小專案。透過這個網頁介面,你可以輕鬆實現體感操作:


設定教學與操作流程

1. MakeCode 專案設定

在使用藍牙連線前,務必先在 MakeCode 進行以下設定:

  1. 點擊 MakeCode 右上角的齒輪 ⚙️。
  2. 選擇「專案設定」(Project Settings)。
  3. 勾選 No Pairing Required (不需要配對)。

2. 產生並燒錄程式

在專案網頁中,你可以選擇想要的「控制方式」與「傳輸介面」,網頁會自動產生對應的程式碼。請切換到 micro:bit MakeCode 的 JavaScript 標籤區,將代碼貼上並上傳至開發板。


micro:bit 體感遊戲控制介面展示


3. 開始玩耍

設定完成後,下方就有一些簡單的體感遊戲可以直接測試玩耍囉!

透過這種方式,就可以用 micro:bit 成了網頁遊戲的搖桿。

【自製運動感測器】用 Micro:bit 測量槓鈴速度:挑戰 1000Hz 高頻採樣的底層開發實錄

在我的重量訓練課表中,除了肌力訓練外,也會加入「爆發力(Power)」訓練,例如舉重衍生動作或壺鈴抓舉。雖然口語說爆發「力」,但在運動科學的定義中,它更精確的定義是「功率」。

爆發力的物理本質

爆發力並非單純的力量,而是功率的表現。我們可以透過以下公式來理解:

爆發力(Power)是力與位移的乘積除以時間,即:
$$ P = \frac{F \times s}{t} = F \times v $$
其中 $F$ 為力量($m \times a$),$v$ 為速度。

換句話說,在處理特定重量時,若能產生更大的加速度與速度,或是在更短的時間內完成更長的位移,就代表爆發力表現越佳。

測量技術的挑戰

在運動科學領域,測量功率通常有兩種路徑:

  • 線性位移感測器 (LPT):測量外部負載(如槓鈴)的位移,透過時間差分算出瞬時速度、加速度與功率。
  • 加速度感測器 (IMU):直接測量負載加速度。雖然理論上可以透過積分算出速度與位移,但微小的加速度偏差在積分過程中會產生巨大的累積誤差。

為了打造自己的測量裝置,我一開始先嘗試使用 Arduino Nano 33 IoT,但電源配置很麻煩,我沒找到可以用的電池方案。後來我想到,其實microbit有很多穿戴式的應用,再加上它也有三軸加速度和藍牙裝置,所以最終我選擇了 Micro:bit V2 作為開發核心。

開發關鍵:突破採樣頻率瓶頸

若要精準測量槓鈴移動,理想的採樣頻率需要達到 1000Hz。然而,標準的積木程式(MakeCode)底層架構無法支援如此高頻的數據傳輸。經過翻閱文件與 AI 協作,我決定從底層進行開發。

核心邏輯如下:

  1. 使用底層 I2C (400kHz) 達成穩定 1ms 的加速度採樣。
  2. 暫存數據,每累積 20 筆資料(約 120 Bytes)封裝為一組 BLE (Bluetooth Low Energy) 封包傳出,以確保傳輸效率。

韌體下載與安裝

我已經將編譯好的 Hex 檔案放在 GitHub,請下載後直接拖入 Micro:bit V2 在電腦上辨識成的隨身碟即可:

下載韌體 (v1.0)

操作說明

Micro:bit 端控制

  • A 鍵:切換感測器量程 (2g, 4g, 8g, 16g)。LED 螢幕首列會亮起對應數量的燈號表示目前量程。
  • B 鍵:開啟或關閉 LED 矩陣顯示。
  • 藍牙配對:開機即自動進入廣播模式,無需 PIN 碼。裝置名稱為 MB_[五個英文字母]

視覺化圖表與網頁介面

數據呈現交給瀏覽器的 Web Bluetooth API。您可以使用 Chrome(Windows/Linux/Android)或 Bluefy(iOS)連結至以下網頁:

線上測量工具網頁

實作與安裝建議

在開始測量前,務必將 Micro:bit 固定後靜止進行重力校準。因為感測器固定在器材上時必然會有傾斜角度,校準後才能獲得正確的垂直向加速度。

關於硬體固定方式,建議搜尋 "microbit 2032 watch" 相關擴充套件,找附帶電池座的穿戴式套件,適合綁在手上或固定在器材上。

校準完成後,您可以透過網頁端的「定時錄製」功能,設定延遲啟動與錄製時長,如同相機自拍設定一般簡單。以下是實際測量與錄製的數據介面參考:

數據結果 1
數據結果 2
所有原始碼都在這,如果你有興趣看看的話

數位電路模擬器

 上個月一個學生問了我CPU裡面是什麼東西,雖然我口頭上簡單說了一些,但是總覺得還想多一些細節描述。回來之後,就想應該可以找數位電路的模擬器來做個web介面來解釋看看吧。

以前我修資訊二專的課程時,在學計算機組織的課程時,就自己找了Digital Logic Sim這個軟體自己玩了一陣子,最後其實是可以搭出CPU的喔,但就所有要自己純手工搭建。

而既然我想要弄個web介面來展示的話,就想讓AI來幫忙建一下。先說結論,最後是有搭出來,但CPU模式還是沒完整成功,但至少前幾個步驟都還是不錯的。

https://chihhsiangchien.github.io/CPU-sim/

我覺得前面幾個step用來解釋內部元件是蠻不錯的。









科展海報設計線上工具

指導科展的倒數工作之一,就是要指導學生製作海報。許多年以前,自己還是國中生的時候,是用一張大白報紙上面先用鉛筆畫格子,然後用麥克筆一個字一個字填到格子裡。

後來出現「非常好色」這樣的軟體後,改成設計海報後再切分成多張A4紙來組合出海報。

如今的作法則是用向量軟體在電腦上設計海報,再將海報檔案交給廠商做大圖印製。當我跟學生說要製作海報時,告訴學生可以用哪些軟體製作,例如powerpoint,不過今年學生倒是直接想用canva來製作,我覺得也好,可以共用也有許多範本可以參考,應該還不錯吧。

但等到他們交出初版海報時,倒是嚇我一跳,居然一點都沒參考範本,幾乎就是報告文字白紙黑字直接複製貼上,且圖片還用低解析度,甚至尺寸還比書面報告小。想想也不能太苛責,畢竟海報設計若要憑空想像版面配置和色彩配置,也實在困難。所以後來又花了一段時間教他們修改與微調,總算是能夠見人了。

交件後,我突然想到,如果來做一個科展海報的範本系統,應該很不錯。其實做海報若不考慮多人協作,我認為inkscape是好選擇,內部其實就是用svg繪圖,而svg又是AI可以直接協作的語言。

總之,我就弄了一個海報範本的系統

https://chihhsiangchien.github.io/sciPosterArchitect/web_builder.html



可以選擇多種色彩系統





每個區塊的外框設計



每個小標題的裝飾和幾何形狀


你也可以修改範本裡的文字來看看效果,最後可以輸出svg檔,用inkscape編輯。就算不用它來編輯,也可以用來做版面配置規劃與色彩選擇。



重回駭入小蟻攝影機

最近想起家裡有一台將近十年的小蟻攝影機,突然覺得可以嘗試讓 AI 協助我,重新駭入這台老設備,認識一下嵌入式系統。

工廠測試後門:記憶卡裡的祕密

當年這台攝影機在社群裡引起轟動,主因是有人發現了一個「後門」:只要在 SD 記憶卡根目錄建立一個名為 test 的資料夾,並在其中放置 equip_test.sh 腳本,系統開機時就會自動執行它。當時的我並不清楚腳本背後的邏輯,但最近透過 AI 的協助解析,終於釐清了它的運作機制。這其實是小蟻韌體在開機時的一個「工廠測試後門」。只要系統偵測到特定名稱的腳本,就會以 Root 權限 執行它,這給了我們完全掌控設備的機會。

腳本核心功能解析

  • 植入永久服務 (Telnet & FTP):在 /etc/init.d/ 建立了 S88telnetS89ftp 檔案,確保設備重啟後依然能透過遠端登入。
  • 替換核心服務 (HTTP & RTSP):將官方受限的版本替換為加強版,例如覆蓋 /home/rtspsvr 以支援標準 RTSP 串流,並建立軟連結(symlink)讓網頁服務能直接讀取錄影檔案。

突破防線:獲取 Root 權限

雖然腳本開啟了 Telnet,但我早已忘記預設密碼。然而我發現 FTP 服務雖然無法讀取系統核心,卻具備寫入權限。於是我就讓AI協助,然後採取了以下策略:

  1. 在本地端製作一個「無密碼 root」的 new_passwd 檔案。
  2. 透過 FTP 強行覆蓋機器內的 /etc/passwd
  3. 成功透過 Telnet 免密碼登入系統。

硬體規格探秘

進入系統後,首先確認了這台機器的「心臟」。原來它是採用鼎鼎有名的 HiSilicon Hi3518 晶片,是一個「自帶後門」的晶片。

# cat /proc/cpuinfo
Processor       : ARM926EJ-S rev 5 (v5l)
BogoMIPS        : 218.72
Features        : swp half thumb fastmult edsp java 
CPU implementer : 0x41
CPU architecture: 5TEJ
CPU variant     : 0x0
CPU part        : 0x926
CPU revision    : 5

Hardware        : hi3518
Revision        : 0000
Serial          : 0000000000000000

系統與記憶體資訊

這是一個典型的嵌入式 Linux 系統,核心版本相當老舊,且記憶體配置極小(僅約 35MB),在現今標準看來非常精簡。

# uname -a
Linux (none) 3.0.8 #1 Wed Apr 30 16:56:49 CST 2014 armv5tejl GNU/Linux

# free   
             total         used         free       shared      buffers
  Mem:        35212        33772         1440            0           56
 Swap:            0            0            0
Total:        35212        33772         1440

當初我的數個定點攝影計畫,許多都仰賴這台攝影機呢,當初的錄製影片儲存路徑,掛載在 /tmp/hd1/record/

資安問題

即便對小蟻相機進行了基礎的 Hack,系統內建的雲端服務依然會嘗試與外部伺服器通訊。

Step 1: 網路狀態初步掃描

首先使用 netstat -atunp 查看目前的連線狀態。可以看到 p2pserver 開啟了 UDP ,這個應該就是用手機連線可以直接穿透內網看到攝影機的關鍵

# netstat -atunp
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
udp        0      0 0.0.0.0:46742           0.0.0.0:* 4363/p2pserver

Step 2: 殺不死的 p2pserver?

當我嘗試 kill -9 4363 (p2pserver) 後,再次檢查網路狀態,發現它竟然以新的 PID (21241) 重新啟動,並且瘋狂嘗試外連!連線的遠端伺服器顯示是 p2pserver 正向百度 (220.181.x.x) 與 AWS (46.137.x.x) 發送 SYN_SENT 請求。

# netstat -atunp
tcp        0      1 192.168.1.104:57766     220.181.111.147:80      SYN_SENT    21241/p2pserver
tcp        0      1 192.168.1.104:35924     46.137.188.54:80        SYN_SENT    21241/p2pserver
tcp        0      1 192.168.1.104:32999     61.188.37.216:8000      SYN_SENT    21241/p2pserver

Step 3: 斬草除根 —— 關閉 watch_process

這代表系統中有監控腳本在維持雲端服務的運作。透過 ps | grep watch,找到了元兇:/home/watch_process

# ps | grep watch
 1096 root       840 S     /home/watch_process
# kill -9 1096

先殺掉監控進程 watch_process,再殺掉 p2pserver,它就不會再自動重啟了。

Step 4: 最終狀態確認

淨化完成: 目前系統僅剩下本地端服務 (FTP, HTTP, Telnet),所有外連嘗試皆已停止。
# netstat -atunp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:21              0.0.0.0:* LISTEN      1248/tcpsvd
tcp        0      0 :::80                   :::* LISTEN      1145/server
tcp        0      0 :::23                   :::* LISTEN      667/telnetd
tcp        0     93 ::ffff:192.168.1.104:23 ::ffff:192.168.1.114:39804 ESTABLISHED 667/telnetd

透過這樣操作,才可以將小蟻相機轉為一台「純區域網路」運作的監控設備,確保影像資料不會在未經許可的情況下傳向公有雲端。如果真的要處理資安問題,應該還是自己弄出的樹莓派機器來操作比較好。

ADS-B 物理學:利用飛機 DAPs 數據重建大氣剖面與飛行分析

在上一篇《ADS-B 數位觀察:分析桃園空域航線、高度與信號傳播特性》中,處理了基本的航跡數據。本篇將進入大氣物理分析。用 R 語言對 DAPs 數據的處理,將每架飛機轉化為高空探空儀,重建出即時的大氣剖面。

數據清洗:確保物理分析的準確性

在進行物理計算前,必須篩選出可靠的樣本。在 dashboard.R 中,確保分析的是處於穩定巡航狀態的噴射機,排除低速或起降階段的干擾:

# 篩選高速、高空且具備完整參數的樣本
df <- df %>%
  filter(!is.na(tas), !is.na(mach), !is.na(alt)) %>%
  filter(tas > 100, mach > 0.3, alt > 5000)

熱力學推導:從馬赫數找回氣溫

高空感測最困難的是獲取準確的「靜溫」(Static Air Temperature),利用真空速 (TAS)馬赫數 (Mach) 的比值來推算音速,進而求得溫度。根據單位換算,節 (knots) 與絕對溫度的關係常數約為 38.945

# 物理計算:音速與溫度
df <- df %>%
  mutate(
    speed_of_sound = tas / mach,
    temp = (speed_of_sound / 38.945)^2 - 273.15
  )
垂直氣溫剖面圖,展示從 DAPs 數據推導出的氣溫隨高度變化趨勢

音速與高度的變化觀測

音速並非恆定,而是溫度的函數。隨著高度增加,氣溫下降,音速也隨之變慢。這在航空物理中至關重要,因為它直接影響了飛機的飛行性能包絡線。

音速與高度關係圖,反映出高空低溫環境對聲速的抑制作用

對流層頂 (Tropopause) 的自動偵測

腳本中還包含了一套簡單的邏輯來預測對流層頂。如果在觀測最高度溫度仍持續下降,代表對流層頂位於更高空:

analyze_tropopause <- function(df) {
  top_alt <- max(df$alt, na.rm = TRUE)
  min_temp <- min(df$temp, na.rm = TRUE)
  return(sprintf("對流層頂預測: 目前觀測最高 %.0f ft 溫度仍下降至 %.1f°C", top_alt, min_temp))
}

風場分析與西風指標 (Westerly Index)

利用飛機的地速 (GS) 與真空速 (TAS) 的差異,我們可以計算航向上的風分量。這對於觀察噴射氣流 (Jet Stream) 的強度非常有幫助。

# 計算高空西風帶強度指標
wind_diff <- avg_wind_east - avg_wind_west # 往東順風與往西逆風的差值
Observed Wind Profile (DAPs),左圖為風速隨高度變化,右圖為風向分佈

D-Value 分析:氣壓偏差的實測

這是腳本中另一個進階分析。飛機回傳的高度有兩種:氣壓高度 (alt)GPS 幾何高度 (alt_geom)。兩者的差值稱為 D-Value

# 計算 D-Value:反映大氣壓與標準大氣的偏差
df <- df %>% mutate(d_value = alt_geom - alt)

這能告訴我們當前空域的氣壓是否偏離標準大氣。正值代表當地氣壓高於標準(高壓區),負值則代表低壓區。

高階建模:空氣密度與能量高度

利用標準大氣模型推導出空氣密度總比能 (Specific Energy Height)

# 計算總比能 (特定能量高度)
df <- df %>%
  mutate(
    v_ms = tas * 0.514444,
    specific_energy = h_m + (v_ms^2) / (2 * g)
  )

0.514444 為節轉公尺/秒之常數,確保能量高度計算之單位一致性。這個指標代表了飛機的「總能量」,是評估飛行效率與性能包絡線的重要指標。

信號與流量分析 (Traffic & Signal)

除了物理參數,我們也對接收站本身的性能進行了建模。例如 RSSI (信號強度)高度 的關係:

從數據中可以清楚觀察到,高度越高,接收到的 RSSI 數值通常越穩定。這背後隱含了兩個關鍵物理機制:

  • 自由空間路徑損耗 (FSPL) 與平方反比定律:無線電能量隨距離平方遞減。雖然高空飛機距離接收站較遠,但因其處於高角度,避開了地表的建築物與地形遮擋。
  • 視距傳播 (Line-of-Sight) 與第一菲涅耳區 (First Fresnel Zone):高空飛機擁有更開闊的傳播路徑,能有效減少「多路徑干擾」(Multi-path interference),避免了地面反射波與直接波互相干涉產生的信號衰落 (Fading)。這也就是為什麼 30,000 英尺以上的信號往往比地平面附近的信號更「純淨」。

航向流量統計 (Heading Rose)

利用圓盤圖展示主要流量的方向性:飛過台灣的航線多是東北西南走向的

性能差異:不同機種的飛行特徵

依照飛機重量類別 (Wake Turbulence Category) 進行分類,觀察 Heavy (如 A350/B777) 與 Small 機種在速度與巡航高度上的差異。

如果你對這些有興趣的話,你可以在這個github repo中找到相關的scripts。

掃地機器人裡的 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

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