7/11巴威颱風的頭前溪溪水
在當天晚上我把定點攝影的影片匯集起來,濃縮成這30秒,就可以看看整個溪水變化的過程
學生物,玩生物! by 阿簡
幾年前作了歷年各科會考分析的通過率鑑別度的視覺化呈現,本來想著每年都更新,但是時間到了卻是懶得做。
拖了四年後,終於前幾天想說就來更新一下,不過這次就不是純人工作業了,而是使用AI AGENT來進行。我直接用antigravity讀取過去那個repo,讓它改寫過去的程式來做更新。
成果非常好,幾十分鐘之後就更新了,並改寫入口網站。
現在從這裡進去就是一個視覺化的網頁,有會考基測的線上互動答題區,也有各科的分析網頁
https://chihhsiangchien.github.io/question_database/index.html
前幾天也利用AI讀取考古題,然後用Latex來編寫了一份講義。可以搭配這裡的互動答題使用。
https://github.com/ChihHsiangChien/question_database/blob/master/biology_handout.pdf
幾年前有幫家人退休金規劃了0050,每當台股來到高點,家人就會問我:「現在要不要先賣掉,等跌下來再接回來?」
面對這種「擇時交易」的迷思,我每次都會說幾個現實:
1.想靠進出賺錢,你必須連續猜對「賣出」與「買回」兩個時間點。這比想像中難得多。
2. 每次交易,你都要問自己:為什麼對方覺得現在該買?你的資訊量真的比擁有 AI 演算法的三大法人多嗎?
3.不要忘記交易手續費與稅,而且賣掉後如果是漲上去,你也不敢買回來啊。
最後我就會說:「如果你真的想挑戰市場,就真的去試試看啊。」但家人通常也只停留在說說。
為了讓這種爭論有個定論,我乾脆弄了一個 App。它會抓取過去真實的歷史資料,讓你進行任選幾回合的交易模擬(預設5回合,每回合 60 個交易日)。
交易結束之後會有結算報告,可以看看到底你的選擇,讓你最後會有多少資產,是否可以接近有上帝視角的完美擇時。
https://optitime-0050-mkzmite3w3vknlpa65mqqg.streamlit.app/?embed=true
最近幾年帶專題研究,主題經常是探討物理與生物系統中的各種應力與應變,像是我們曾經研究過的粉圓受壓實驗。
我學到的是,如果我們只是想著抵抗壓力,不斷武裝自己,拒絕讓外在壓力產生任何改變。這種結果往往是累積耗損,最後就會在某個臨界點直接脆斷、崩潰。
但如果將重點轉向「韌性」,既能承受壓力,也允許系統產生應變。即便壓力大到導致顯著變形,只要在壓力過後能夠「釋放應力」與「彈性恢復」,就像拉扯橡皮筋後又回到原來狀態,我們就不必害怕變形帶來的後果。
昨天的強降雨,城市該如何應對?傳統思維偏向建造高聳的防洪堤,試圖將大水完全拒於城外;這是以「全面抵抗」作為對策,但一旦堤防潰堤,災難往往比自然漫流更劇烈。
反之,若我們承認降雨(壓力)的不可抗力,轉而透過透水鋪面與滯洪池,讓水流在結構中自然滲透、產生應變,將水暫時留存、慢慢消化。在這過程中,城市雖看似被淹沒了,但這是一種受控、短暫且具備彈性的狀態。
透過這種適度的變形與吸納,城市反而保全了整體骨架,待雨過天晴,又能迅速恢復原樣。
面對內心的壓力也是如此。當壓力突襲,若選擇築起高牆武裝自己,一旦壓力突破極限值,最終就是崩潰。
反之,如果能建立內心的滯洪池,允許自己在壓力高峰期產生適度的焦慮或挫折,給予情緒緩衝的時間與空間。待壓力平息後,再透過心理的排水系統沉澱或抒發,代謝掉累積的壓力。
真正的韌性,不在於強求毫髮無傷,而在於懂得允許自己變形,並在壓力離去後,具備回歸平衡狀態的能力。
下雨過後,常常可以看見雲氣繚繞山頭,它們從哪裡湧現?又在何時消散?肉眼凡胎縱使屏氣凝神看它一整天,也不可能洞悉,因為腦中無法疊合每一分鐘的雲氣變幻,即便它們如此顯眼。
此時,只有將時間資訊壓縮,數據才會浮現。
這是6/27一早的定點長時間拍攝,前一天才是新竹的強降雨,將四小時的緩慢對流壓縮成一分鐘的縮時影片,規律清晰可見。
觀察山嵐的變化,就會明白許多事物之所以顯得無常,往往只是因為觀測的時間尺度太短。
在大自然的視角下,雲氣消散並非結束,只是轉換型態隨風遠行;繚繞也並非只是停留,而是不斷在山頭間流動。
我們以為的消失,是進入視覺上的不可見;而我們以為的無常,也只是恆常的一部分。
學校裡有一車八年的舊款 HP 筆電,裝的是 Windows OS,很奇怪,wifi網路連線的品質很差,總是經常斷線。因為一直不能解決,後來我看它也老了,微軟也即將不支援了,乾脆把它們全面遷移至 Linux Mint。
最近著迷於硬體的逆向工程,回想幾年前還沒有資安意識時,買了很多小米系列產品,最近把它們全翻出來,收歸它們的數位主權。
做法是植入一個開源的 Valetudo 進去,直接控管內部,阻斷所有資料回傳伺服器,把控制權從雲端拿回本地。深入挖掘後發現其底層是 ubuntu,cpu還是當年會用來作安卓平板的,難怪裏頭還有GPU。再更深入探索機器人的實作程式,原來用的是開源的 Player Project。
這個早就被找出後門的機器,以前就駭過但不知細節。現在多了資訊能力更能深入其中,裏頭是 linux 嵌入系統,硬體用的是因後門爭議而聞名的海思晶片。總之駭入後,就可以關掉一些程序,徹底收編為我個人的監控節點。
刷入 OpenWrt,這是一個基於 Linux 的開源嵌入式作業系統,就像收編間諜一樣,可以徹底管控它。
玩了這些還不夠,最近又取得具有高通 SDM845 晶片的手機,因為這款晶片已被 linux 開源社群開發併入 Linux Mainline。具備這款晶片的手機,可以刷成純 linux 的系統,這樣可以作為開發小型應用的行動工作站,拿著手機就像拿著電腦一樣。
最近學校有個教室配置了多個大型互動顯示器,還併排成在一起,但使用上就是多個獨立螢幕。於是我進入了系統內部研究了,就把它們當作大型安卓平板進行架構分析。評估系統效能後,在其中一台運行 Node.js 作為網頁伺服器,透過 WebSocket 同步機制強制三塊螢幕渲染同一網頁內容,這樣就可以實現了拼接顯示。後來持續進行的是,還讓每個螢幕各自連接網路攝影機,並在本機的 GPU 執行 AI 姿勢偵測,直接將這些顯示器轉化為大型的互動體感裝置。
國中生物會教到兩次渦蟲,一次在無性生殖,第二次在扁形動物。課堂上播放的影片總是讓學生一提到渦蟲就直呼「好可愛!」、紛紛說著想把牠「切一切」,甚至還有幾個學生興致勃勃地說好想帶回家養。
聽學生說了幾次之後,我就隨口應了一句:「那我弄個數位板的渦蟲給你們養吧!」
就像過去開發教學工具一樣,先在腦海中想好具體目標,接著透過 AI Agent 協作,這款療癒系的「數位渦蟲養殖場」就這麼來了!
線上連結:
https://chihhsiangchien.github.io/pinky-planarian/
這款養殖模擬器利用了物理模擬技術,呈現出渦蟲那種軟綿綿、四處游動的動態。在功能設計上可以進行餵食,觀察牠們長大,還可以切斷再生。
網頁支援 PWA (Progressive Web App) 技術。使用者可以像安裝手機 App 一樣,直接將這個網頁添加到手機的主畫面,隨時隨地開啟這個口袋渦蟲水族箱。
系統的互動工具如下:
幾年前買了一個熱影像攝影模組/紅外線陣列 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 端的程式碼:
#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 ,加上了滑鼠移動即時顯示該點「真實溫度」的功能!
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 的畫面投影到教室的大螢幕上了!就能拿來做生物體溫、植物蒸散作用或環境物理性質的演示。
上週研習時,學校的君翰老師帶大家做了電磁學的教具,原型是其他老師去科工館看到的。原本的設計是透過繞線圈,觀察通電後的磁場改變如何使得指北針偏轉。
四周有兩處缺刻的壓克力板,對於固定繞線圈十分友善。看著繞好的線圈,我突然有了靈感,好像可以把它改造成其他教具?於是,我跟同事要了一套他的材料,拿回來摸索一下,最後成功做出了一個「無線傳電教具」。
先來看看實際的展示效果。下圖左邊是發射線圈(加了一些電子元件,後面會詳細說明),右邊則是接收線圈,結構非常純粹:只有漆包線圈直接接上一顆紅光 LED 燈。
實驗操作時,完全不需要任何實體導線連接,只要將接收線圈逐漸靠近發射線圈,右側的紅光 LED 就會神奇地亮起來!
動態演示影片如下:
這個無線傳電教具的核心在於發射端的「自激震盪電路」。電路的繞線與接線方式如下:
自激震盪與電磁感應:
這個電路的核心目的,是利用電晶體達成極高頻率的「導通與斷電」切換。通電瞬間,連接 B極 的線圈導電並產生初始磁場,此磁場的變化使連接 C極 的線圈產生感應電動勢,進而誘發更大的電流。這個增強的電流反過來進一步推動 B極,使電晶體迅速達到「飽和導通」。當電晶體飽和後,電流不再變化,磁場隨之停止增長;失去了變化的磁場,B極 失去推動力,電晶體隨即斷電。斷電後整個過程再度重演。
透過上述的循環,發射線圈便能產生高頻率的電磁震盪。此時,一旁的接收線圈切換進入這個快速變化的磁場中,根據法拉第電磁感應定律,接收線圈內就會產生感應電流,成功推動紅光 LED 發光。
除了使用研習提供的壓克力板作為繞線骨架外,其實我一開始進行概念驗證時,是直接拿市售整捆未拆的漆包線圈來做發射端的。當時先把線圈拆開重新繞製,抓出中央抽頭;而接收端則是隨手繞在普通的紙杯或塑膠杯上。這種作法,也適合作為學生探究與實作的課堂材料,在此一併分享給大家參考。
我在學校有開一個給資優生的選修課,用的是科學魔法車,是教學生電子電路應用和寫程式。
在這個課程中,用的是曹老師設計的教材,其中有一個電路很有趣,是我會多花一點時間給學生玩的,就是這個兩級 RC 弛張震盪器 (RC Relaxation Oscillator)
用的就是一顆反向器IC 4069,藉由四個反向器搭建出來的高頻與低頻振盪電路,確定會發出聲音之後,下一步就可以把30 KΩ 和 20 KΩ 換成可變電阻,然後就可以任意用這兩個電阻創造不同的聲音。
最後再把可變電阻換掉,變成兩條鱷魚夾。然後準備一張紙,紙上用2B鉛筆畫出筆跡,延伸到紙緣,用其中一條鱷魚夾夾住。另外一個鱷魚夾就可以在筆跡上面移動,就像是可變電阻那樣控制出不同的聲音。
如果設計得當,甚至是可以創造出音階,好好唱一首歌曲喔。
前一陣子都在玩 micro:bit,嘗試用它來做各項應用。回憶起很早以前玩 Scratch 時,除了在電腦上直接按鍵控制角色外,也可以透過一些軟硬體套件配合,利用 Arduino 來連動。後來也有軟體搭配,讓 micro:bit 能夠控制 Scratch 角色。這讓我想到:我應該可以繞過這些複雜的套件,直接用 micro:bit 來控制網頁遊戲吧?
結合以下三個核心特色,就能達成硬體與網頁遊戲的互動:
基於上述構想,我製作了一個小專案。透過這個網頁介面,你可以輕鬆實現體感操作:
在使用藍牙連線前,務必先在 MakeCode 進行以下設定:
在專案網頁中,你可以選擇想要的「控制方式」與「傳輸介面」,網頁會自動產生對應的程式碼。請切換到 micro:bit MakeCode 的 JavaScript 標籤區,將代碼貼上並上傳至開發板。
設定完成後,下方就有一些簡單的體感遊戲可以直接測試玩耍囉!
透過這種方式,就可以用 micro:bit 成了網頁遊戲的搖桿。
在我的重量訓練課表中,除了肌力訓練外,也會加入「爆發力(Power)」訓練,例如舉重衍生動作或壺鈴抓舉。雖然口語說爆發「力」,但在運動科學的定義中,它更精確的定義是「功率」。
爆發力並非單純的力量,而是功率的表現。我們可以透過以下公式來理解:
爆發力(Power)是力與位移的乘積除以時間,即:
$$ P = \frac{F \times s}{t} = F \times v $$
其中 $F$ 為力量($m \times a$),$v$ 為速度。
換句話說,在處理特定重量時,若能產生更大的加速度與速度,或是在更短的時間內完成更長的位移,就代表爆發力表現越佳。
在運動科學領域,測量功率通常有兩種路徑:
為了打造自己的測量裝置,我一開始先嘗試使用 Arduino Nano 33 IoT,但電源配置很麻煩,我沒找到可以用的電池方案。後來我想到,其實microbit有很多穿戴式的應用,再加上它也有三軸加速度和藍牙裝置,所以最終我選擇了 Micro:bit V2 作為開發核心。
若要精準測量槓鈴移動,理想的採樣頻率需要達到 1000Hz。然而,標準的積木程式(MakeCode)底層架構無法支援如此高頻的數據傳輸。經過翻閱文件與 AI 協作,我決定從底層進行開發。
核心邏輯如下:
我已經將編譯好的 Hex 檔案放在 GitHub,請下載後直接拖入 Micro:bit V2 在電腦上辨識成的隨身碟即可:
MB_[五個英文字母]。數據呈現交給瀏覽器的 Web Bluetooth API。您可以使用 Chrome(Windows/Linux/Android)或 Bluefy(iOS)連結至以下網頁:
在開始測量前,務必將 Micro:bit 固定後靜止進行重力校準。因為感測器固定在器材上時必然會有傾斜角度,校準後才能獲得正確的垂直向加速度。
關於硬體固定方式,建議搜尋 "microbit 2032 watch" 相關擴充套件,找附帶電池座的穿戴式套件,適合綁在手上或固定在器材上。
校準完成後,您可以透過網頁端的「定時錄製」功能,設定延遲啟動與錄製時長,如同相機自拍設定一般簡單。以下是實際測量與錄製的數據介面參考:
上個月一個學生問了我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
每個區塊的外框設計
每個小標題的裝飾和幾何形狀
最近想起家裡有一台將近十年的小蟻攝影機,突然覺得可以嘗試讓 AI 協助我,重新駭入這台老設備,認識一下嵌入式系統。
當年這台攝影機在社群裡引起轟動,主因是有人發現了一個「後門」:只要在 SD 記憶卡根目錄建立一個名為 test 的資料夾,並在其中放置 equip_test.sh 腳本,系統開機時就會自動執行它。當時的我並不清楚腳本背後的邏輯,但最近透過 AI 的協助解析,終於釐清了它的運作機制。這其實是小蟻韌體在開機時的一個「工廠測試後門」。只要系統偵測到特定名稱的腳本,就會以 Root 權限 執行它,這給了我們完全掌控設備的機會。
/etc/init.d/ 建立了 S88telnet 與 S89ftp 檔案,確保設備重啟後依然能透過遠端登入。/home/rtspsvr 以支援標準 RTSP 串流,並建立軟連結(symlink)讓網頁服務能直接讀取錄影檔案。雖然腳本開啟了 Telnet,但我早已忘記預設密碼。然而我發現 FTP 服務雖然無法讀取系統核心,卻具備寫入權限。於是我就讓AI協助,然後採取了以下策略:
new_passwd 檔案。/etc/passwd。進入系統後,首先確認了這台機器的「心臟」。原來它是採用鼎鼎有名的 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,系統內建的雲端服務依然會嘗試與外部伺服器通訊。
首先使用 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
當我嘗試 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
這代表系統中有監控腳本在維持雲端服務的運作。透過 ps | grep watch,找到了元兇:/home/watch_process。
# ps | grep watch
1096 root 840 S /home/watch_process
# kill -9 1096
先殺掉監控進程 watch_process,再殺掉 p2pserver,它就不會再自動重啟了。
# 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 數位觀察:分析桃園空域航線、高度與信號傳播特性》中,處理了基本的航跡數據。本篇將進入大氣物理分析。用 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
)
音速並非恆定,而是溫度的函數。隨著高度增加,氣溫下降,音速也隨之變慢。這在航空物理中至關重要,因為它直接影響了飛機的飛行性能包絡線。
腳本中還包含了一套簡單的邏輯來預測對流層頂。如果在觀測最高度溫度仍持續下降,代表對流層頂位於更高空:
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))
}
利用飛機的地速 (GS) 與真空速 (TAS) 的差異,我們可以計算航向上的風分量。這對於觀察噴射氣流 (Jet Stream) 的強度非常有幫助。
# 計算高空西風帶強度指標
wind_diff <- avg_wind_east - avg_wind_west # 往東順風與往西逆風的差值
這是腳本中另一個進階分析。飛機回傳的高度有兩種:氣壓高度 (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 為節轉公尺/秒之常數,確保能量高度計算之單位一致性。這個指標代表了飛機的「總能量」,是評估飛行效率與性能包絡線的重要指標。
除了物理參數,我們也對接收站本身的性能進行了建模。例如 RSSI (信號強度) 與 高度 的關係:
從數據中可以清楚觀察到,高度越高,接收到的 RSSI 數值通常越穩定。這背後隱含了兩個關鍵物理機制:
利用圓盤圖展示主要流量的方向性:飛過台灣的航線多是東北西南走向的
依照飛機重量類別 (Wake Turbulence Category) 進行分類,觀察 Heavy (如 A350/B777) 與 Small 機種在速度與巡航高度上的差異。
如果你對這些有興趣的話,你可以在這個github repo中找到相關的scripts。多年前買了小米掃地機器人之後,我一直很好奇它到底在背景偷偷把什麼東西傳回伺服器。為了揭開這個黑盒子,我曾嘗試將電腦偽裝成 SSID 的 Gateway,透過 ARP 欺騙 (ARP spoofing) 的方式攔截封包。雖然成功拿到了數據,但除了目的地之外,封包內容依然處於加密狀態,難以直接解密。
更令人不安的是,這台機器人傳輸封包的頻率極高,它可是一直傳、一直傳,這種「不受控」的感覺實在令人不爽。直到後來,我偶然發現了 Valetudo 方案,這才解決了我的隱私焦慮。
簡單來說,Valetudo 就像是在掃地機器人的系統裡放了一個寄生蟲。它會攔截原本要送往遠端伺服器的資料,將其導流至機器人內部的本地空間處理,隨後便銷毀與雲端的聯繫。這讓你的掃地機器人徹底脫離雲端監控,變成一台純粹的區網設備。
安裝過程其實不算複雜,但對網路環境的要求較高。以下是我的實作筆記:
首先,需要將機器人重置:先按下 Home 鍵,接著拿迴紋針戳一下 Reset 鍵後放開。過一陣子,機器人會發出語音提示正在回復原廠設定,大約需要五分鐘的時間。
以我的小米掃地機器人一代為例,我使用了 這個專屬連結。在構建過程中,需要填入個人資訊與 SSH 公鑰(Key),這樣後續才能直接透過終端機遠端登入。
在燒錄工具的選擇上,我一開始在 Windows 環境下屢試不爽,推測是防火牆或網路設定干擾。後來改用 Linux (AMD64) 筆電,關閉防火牆與 Tailscale 等網路服務後,一次就成功了。
待機器人重置完成並連上它的臨時 Wi-Fi 後,執行以下指令開始植入:
./valetudo-helper-miioota-linux-x64 install-firmware v11_004018.pkg
不到 10 分鐘,Valetudo 就植入完成了!現在我不僅能使用網頁介面操作,還能直接透過 SSH 登入機器人的後台。
登入後,我驚訝地發現這台機器的規格相當高規格:
這本質上就是一台 Linux 自走車!這也解釋了為什麼它能記錄這麼多隱私資訊。在未「去雲端化」前,它會上傳以下三類資料:
當你刷入 Valetudo 後,你實際上是在機器內部建立了一道「數位邊界」。以下是處理前後的差異:
| 資料類型 | 官方韌體 (上傳北京) | Valetudo (Root 後) |
|---|---|---|
| 地圖數據 | 上傳伺服器再回傳手機 | 留在內存,由網頁直接讀取 |
| Wi-Fi 掃描 | 定期回報周邊環境 | 攔截,資料僅存於本地日誌 |
| OTA 更新 | 廠商強制或提示更新 | 徹底關閉,掌握主控權 |
| 通訊加密 | 伺服器持有 Key | 你持有 Token,區網內連線 |
在 /mnt/data/rockrobo/rrlog 看到的資料夾,原本是這些敏感資料上傳前的「暫存區」,現在它們哪裡也去不了了。
掌握了這台 Linux 自走車後,我開啟了一個 AI Agent,讓它透過 SSH 在機器人內部到處巡檢,並撰寫 Script 驗證各種溝通方式。這部分的技術細節我也整理在我的專案中:
Roborock V1 Research - GitHub Project
這個專案不僅揭露了軟硬體結構,還包含如何透過 Python、API 與 MQTT 與它溝通。我最近做了一個有趣的實驗:
結合這兩個特性,我已經能寫 Script 讓它走出特定的路徑,像是三角形、甚至正八邊形。相比起自己找材料組裝的自走車,這台機器人的感測器與底盤配備根本超棒。
目前二手市場的小米掃地機器人一代大約只要 1000 元台幣 左右。如果您對 Linux 開發、自動導航或自走車專案有興趣,這絕對是一個物超所值的硬體平台。只要花一點心思刷入 Valetudo,它就不再是隱私外洩的潛在漏洞,而是一個完全受你掌控的強力研究工具。
我與 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 方式部署,捨棄傳統壓縮包,這讓未來的安全性更新與版本追蹤都能透過簡潔的指令無痛完成,大幅降低了長期維護的難度。
站台架好已經一年了,架站過程中有大量 Linux 指令,一定會讓人看了就害怕,但事實上,現在有了 AI 的協助,一切都變得很簡單。不需要死背指令或翻文件,只需將精確的需求告訴 AI,它就會產出對應的步驟。我的工作變成了:提出需求、複製 AI 產出的指令、貼進終端機,若遇到報錯就再貼回給 AI 進行診斷。完成一部分工作後,再讓它寫md給我做紀錄
雖然我沒試過,但是我相信現在可以直接在 Linux 機器上利用 AI Agent直接完成所有設定,例如安裝opencode或GEMINI CLI之類的AGENT來直接用終端機下指令,因為 99% 的架站工作都是在終端機內完成,直接可以完成排錯與優化。
當我們站在岸邊或甲板上遠眺,如果要知道海面上的船隻或是其他物體的距離,光靠目測是非常不可靠的。這時候你需要一根「測距桿」來幫你測量距離。
這套精確測距系統的核心公式,源自於 D. Heinemann 於 1981 年發表在《野生動物管理期刊》的研究:"A range finder for pelagic bird censusing"。這篇文章解決了海上觀測最大的難題:地球是圓的。在長距離觀測中,海平面會向下彎曲,Heinemann 引入了「視地平線」概念,確保了測量的嚴謹性。
1. 基礎階段:假設地球是平的
若海面是平的,我們利用相似三角形原理:眼睛為頂點,臂長與木桿構成小三角形,眼高與距離構成大三角形。比例關係為 $x / b = h / d$,簡單直觀。
2. 進階階段:現實的曲面修正
現實中,地平線並非無限遠。如論文原圖所示,視線會與地球弧面相切。木桿頂端對準的是「視地平線 $v$」,而目標 $d$ 位於地平線「之下」。這兩條視線在木桿上截出的間距,就是我們需要的精確刻度 $C_i$。
$C_i = \frac{b \cdot h \cdot (v - d)}{v \cdot d}$
如果你需要製作多種測距桿,可以使用 Excel 來計算。請參考以下步驟:
設定欄位名 (A欄):
輸入數值與公式(B欄):
=3838 * SQRT(B1)=B2*B1*(B4-B3)/(B4*B3)*100延續前一篇開發的「即時心率顯示圖表」並順利上架後,我打算重啟一個三年前的挑戰,在手錶上繼續開發心率變異性(HRV)的圖表。
這項計畫的核心在於分析「連續兩次心跳的時間間隔」(RR Interval),藉此觀察人體生理狀態的微妙變化。目前主流穿戴裝置(如 Apple Watch、Garmin)都有內建 HRV 偵測,並以此推算使用者的壓力指數。
而我這次的目標,是直接利用這些心跳間隔數據,繪製出能展示心臟動態規律的 龐加萊圖(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é 散佈圖提供不同的「視角」:
在 Poincaré 圖左上角即時更新的數據,是透過將心跳點群旋轉 45 度後的幾何投影計算得出的。
除了三張不同縮放比例的Poincaré 圖外,另外還有兩張圖
Poincaré 圖:橢圓明顯「變胖」。點往兩側延伸,SD1 顯著增加。
Autonomic State Map (ASM):即時狀態(橙色點)會向右方(SD1 軸)飄移,顯示煞車力道增強。
Energy vs Balance 圖:點會往「右上角」移動,代表能量儲備(面積)增加且系統趨於平衡。
當身體進入「戰或逃(Fight-or-Flight)」模式時,交感神經會接管主導權。為了維持穩定的輸出,心臟會像精密運行的時鐘,心跳間期的變異會大幅縮小。
Poincaré 圖 (Wide Mode):橢圓會變得極度「乾癟」且「細長」。所有的點緊緊貼附在 $y=x$ 的對角線上,代表 $RR_n$ 與 $RR_{n+1}$ 幾乎相等,SD1(短軸)幾乎消失。
Autonomic State Map (ASM):即時狀態(橙色點)會向左方與上方移動(靠近 Y 軸),反映出 SD1(煞車)極低,而由交感主導的規律性讓點留在高位。