2026/4/16

Attempting to Bring Up Realtek RTL8922BE on Ubuntu 24.04 (Failed)

Hardware: Realtek Semiconductor Co., Ltd. RTL8922BE [10ec:8922] (rev 01)

Operating System: Ubuntu 24.04 LTS (Kernel 6.8.0-107-generic)


Step 1: Hardware Identification and Initial Setup

The system was found to have a Realtek RTL8922BE Wi-Fi 7 card, but no wireless interface was present. Initial checks showed the rtw89_8922be driver was missing from the default Ubuntu kernel.

Installed necessary build tools and headers:

sudo apt install git build-essential linux-headers-$(uname -r) network-manager iw

Step 2: Building the Driver from Source

We used the official backport repository for Realtek rtw89 drivers maintained by lwfinger:

git clone https://github.com/lwfinger/rtw89.git
cd rtw89
make
sudo make install
    

After installation, we encountered an Exec format error during modprobe due to conflicts with existing partial kernel modules. We successfully resolved this by unloading all related modules (rtw89_core, rtw89_pci, etc.) before loading the new rtw_8922ae driver.

Step 3: Firmware Issues and Troubleshooting

The driver successfully loaded but failed to initialize the hardware with the error: no suitable firmware found and failed to recognize firmware.

  • Discovery: The driver was looking for rtw8922a_fw.bin. We downloaded the latest versions (v1, v2, v3, v4) from the Linux Firmware repository.
  • Hardware Mismatch: We added debug logging to the driver and discovered the hardware is Cut C (CV=2). However, all publicly available firmware files (even the latest v4) only contain support for Cut A (CV=0) and Cut B (CV=1).

Step 4: Attempted Fallback and Final Failure

As a last-ditch effort, we patched the driver's firmware loading logic (fw.c) to force the hardware to accept the CV=1 firmware as a fallback for the CV=2 chip.

Result:

  • The driver successfully initialized and created the wlp7s0 interface.
  • The interface was visible in nmcli.
  • However, the hardware failed internal calibration with errors: failed to do RF RX_DCK result from state 4 and HW scan failed with status: -14.

Conclusion

The Realtek RTL8922BE Cut C (CV=2) revision found in this server is too new for the current publicly available drivers and firmware. While the driver can be "hacked" to load, the firmware for earlier revisions is physically incompatible with the RF calibration requirements of the newer chip revision.

Recommendation: Users with this hardware revision must wait for Realtek to release an updated firmware file (likely rtw8922a_fw-5.bin) that explicitly supports the CV=2 hardware revision.

2026/4/12

Kronos 技術深度分析:金融K線首個基礎模型

1. 為何需要專門的金融K線 Foundation Model?

時間序列基礎模型(TSFM)在電力、醫療等領域已有成功案例,但金融 K線資料面臨獨特挑戰:

  • K線具有多維結構(OHLCVA),難以直接套用一般 Transformer
  • 市場具有高噪聲、非穩態特性,過去規律不一定適用未來
  • 不同交易所的交易規則、結算制度、漲跌限制各異,跨市場泛化困難

Kronos(arXiv:2508.02739,AAAI 2026)針對這些問題提出系統性解決方案,是第一個開源的金融 K線基礎模型。

2. 技術架構:兩階段框架

Stage 1:K-line Tokenizer(BSQ)

使用 Binary Spherical Quantization 將連續多維的 OHLCVA 向量量化為離散 tokens。這個 tokenizer 並非一般 NLP 的 BPE,而是針對金融市場價格動態專門設計,能保留價格區間、相對變化等關鍵資訊。

Stage 2:Autoregressive Transformer

Decoder-only 架構,在量化後的 token 序列上做自回歸預測。預訓練目標為「預測下一個 K-line token」,與 GPT 系列語言模型的訓練邏輯一致。

3. 模型版本與選擇

模型TokenizerContext Length參數量開源
Kronos-miniKronos-Tokenizer-2k20484.1M
Kronos-smallKronos-Tokenizer-base51224.7M
Kronos-baseKronos-Tokenizer-base512102.3M
Kronos-largeKronos-Tokenizer-base512499.2M

預訓練規模:120 億筆 K線記錄、45 個交易所、7 種時間粒度。
HuggingFace:NeoQuasar/Kronos-small

4. 台灣股市應用評估

優勢

  • 預訓練涵蓋45個交易所、120億筆 K線,台灣屬亞洲新興市場,已有跨市場泛化能力
  • Zero-shot RankIC 領先其他 TSFM 93%
  • Kronos-mini(4.1M)可在一般 GPU 執行,適合個人或小型團隊研究
  • 不需自行設計特徵工程或時間序列標記

限制

  • Context Length 限制(512 ~ 2048),日K約1.4年至8年
  • 不看財報、總經、籌碼等非價格資訊
  • 台灣特有的政策干預、權值股主導特性未被訓練進去
  • ±10% 漲跌限制、T+2 結算等台灣制度細節,建議透過 Fine-tuning 針對優化

5. 建議流程

  1. 回測優先:以歷史日K做滾動窗口回測,觀察模型在多頭、空頭、震盪不同市場型態的表現
  2. 參考輔助:Kronos 預測結果作為技術面輔助參考,不作唯一進出场依據
  3. 長期評估 Fine-tuning:如欲針對台股特性微調,需較大 GPU 資源與乾淨歷史資料

參考連結

Kronos GitHubgithub.com/shiyu-coder/Kronos
Kronos 論文arxiv.org/abs/2508.02739
HuggingFace 模型NeoQuasar/Kronos-small
twstock 套件github.com/mlouielu/twstock

研究時間:2026-04-12

Kronos 安裝腳本與台灣股市預測實作

1. 安裝腳本

#!/bin/bash
# Kronos 安裝腳本(Python 需 3.10+)
set -e

KRONOS_DIR="/home/charles-chang/.openclaw/workspace/research/2026-04-12_kronos_taiwan_stock/kronos_repo"
ENV_DIR="/home/charles-chang/kronos_env"

# 建立虛擬環境
if [ ! -d "$ENV_DIR" ]; then
    python3 -m venv $ENV_DIR
fi
source $ENV_DIR/bin/activate

# 安裝依賴
pip install --upgrade pip -q
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 -q
pip install yfinance pandas transformers einops -q

# 克隆 Kronos(如尚未存在)
if [ ! -d "$KRONOS_DIR" ]; then
    git clone https://github.com/shiyu-coder/Kronos.git $KRONOS_DIR
fi

# 驗證模型
python3 <<'PYEOF'
import sys
sys.path.insert(0, '/home/charles-chang/.openclaw/workspace/research/2026-04-12_kronos_taiwan_stock/kronos_repo')
from model import Kronos, KronosTokenizer, KronosPredictor
tokenizer = KronosTokenizer.from_pretrained("NeoQuasar/Kronos-Tokenizer-base")
model = Kronos.from_pretrained("NeoQuasar/Kronos-small")
predictor = KronosPredictor(model, tokenizer, max_context=512)
print("模型載入成功 ✓")
PYEOF

2. 台灣股市預測腳本

#!/usr/bin/env python3
"""
Kronos 台灣股市預測腳本
使用:python3 predict_taiwan_stock.py <股票代號> [--lookback N] [--pred_len N]
範例:python3 predict_taiwan_stock.py 2330 --pred_len 30
"""

import os, sys, argparse
from datetime import datetime, timedelta
import pandas as pd
import yfinance as yf

kronos_path = os.path.join(os.path.dirname(__file__), 'kronos_repo')
if os.path.exists(kronos_path):
    sys.path.insert(0, kronos_path)
from model import Kronos, KronosTokenizer, KronosPredictor


def fetch_taiwan_stock_data(stock_code: str, days: int = 800) -> pd.DataFrame:
    """從 Yahoo Finance 取得台股歷史 K線"""
    ticker = f"{stock_code}.TW"
    df = yf.download(ticker, period=f"{days}d", auto_adjust=False, progress=False)
    df = df.reset_index()
    if isinstance(df.columns, pd.MultiIndex):
        df.columns = [col[0] if isinstance(col, tuple) else col for col in df.columns]
    rename_map = {}
    for col in df.columns:
        cl = str(col).lower()
        if 'date' in cl: rename_map[col] = 'timestamps'
        elif 'open' in cl and 'open' not in rename_map.values(): rename_map[col] = 'open'
        elif 'high' in cl and 'high' not in rename_map.values(): rename_map[col] = 'high'
        elif 'low' in cl and 'low' not in rename_map.values(): rename_map[col] = 'low'
        elif 'close' in cl: rename_map[col] = 'close'
        elif 'volume' in cl and 'volume' not in rename_map.values(): rename_map[col] = 'volume'
    df = df.rename(columns=rename_map)
    df = df.loc[:, ~df.columns.duplicated()]
    df['amount'] = 0.0
    for col in ['open', 'high', 'low', 'close', 'volume', 'amount']:
        df[col] = pd.to_numeric(df[col], errors='coerce')
    df = df.dropna()
    return df[['timestamps', 'open', 'high', 'low', 'close', 'volume', 'amount']]


def predict(df: pd.DataFrame, lookback: int = 400,
             pred_len: int = 30, sample_count: int = 1) -> pd.DataFrame:
    """Kronos-small K線預測"""
    tokenizer = KronosTokenizer.from_pretrained("NeoQuasar/Kronos-Tokenizer-base")
    model = Kronos.from_pretrained("NeoQuasar/Kronos-small")
    predictor = KronosPredictor(model, tokenizer, max_context=512)
    lookback = min(lookback, 512)
    df_in = df.tail(lookback).reset_index(drop=True)
    x_ts = df_in['timestamps'].reset_index(drop=True)
    y_ts = pd.Series(pd.date_range(
        start=df_in['timestamps'].iloc[-1] + timedelta(days=1),
        periods=pred_len, freq='B'))
    return predictor.predict(
        df=df_in[['open', 'high', 'low', 'close', 'volume', 'amount']],
        x_timestamp=x_ts, y_timestamp=y_ts,
        pred_len=pred_len, T=1.0, top_p=0.9, sample_count=sample_count)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument('stock_code', help='股票代號(例:2330)')
    ap.add_argument('--lookback', type=int, default=400)
    ap.add_argument('--pred_len', type=int, default=30)
    ap.add_argument('--sample_count', type=int, default=1)
    args = ap.parse_args()
    df = fetch_taiwan_stock_data(args.stock_code, days=int(args.lookback * 2))
    pred = predict(df, args.lookback, args.pred_len, args.sample_count)
    print(f"\n{'日期':<12} {'Open':>10} {'High':>10} {'Low':>10} {'Close':>10}")
    print("-" * 60)
    for idx, row in pred.iterrows():
        ds = idx.strftime('%Y-%m-%d') if hasattr(idx,'strftime') else str(idx)
        f = lambda k: f"{row.get(k,0):>10.2f}" if isinstance(row.get(k,0),(int,float)) else f"{str(row.get(k,'')):>10}"
        print(f"{ds:<12} {f('open')} {f('high')} {f('low')} {f('close')}")
    out = f"prediction_{args.stock_code}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
    pred.to_csv(out, index_label='timestamps')
    print(f"\n已儲存:{out}\n此為模型輸出結果,僅供參考,不構成投資建議。")


if __name__ == "__main__":
    main()

3. 台灣股市資料來源

Yahoo Finance(yfinance)日K為主,即時性普通,3行代碼即完成
twstock 套件台灣原生,直接串接證交所與櫃買中心
Fugle API日內分鐘線最可靠
證交所開放資料 API官方 REST,即時性高
# Yahoo Finance 最簡範例
import yfinance as yf
df = yf.download("2330.TW", start="2020-01-01")  # 台積電

4. 使用方式

# 安裝環境
bash setup_kronos.sh

# 預測台積電未來30天
/home/charles-chang/kronos_env/bin/python3 predict_taiwan_stock.py 2330

# 指定歷史窗口與預測長度
/home/charles-chang/kronos_env/bin/python3 predict_taiwan_stock.py 2330 --lookback 200 --pred_len 10

5. 台股特殊考量

  • 漲跌停 ±10%:Fine-tuning 時不應過濾這些bars,是正常市場現象
  • 成交金額(amount):Yahoo Finance 無此欄位,以0填補即可
  • Context Length:Kronos-small 512 tokens(約1.4年日K);日內分鐘線建議用 Kronos-mini(2048)
  • 日內資料:Yahoo Finance 分鐘線在台股不可靠,建議改用 Fugle API

實作完成時間:2026-04-12

如何突破 Cloudflare 取得 Perplexity 內容

問題背景

在自動化研究場景中,常常需要透過 AI 搜尋引擎(如 Perplexity)抓取整理過的答案,並進一步餵給其他系統處理。然而 Perplexity 的分享連結受到 Cloudflare JS Challenge 保護,大多數程式化的存取方式都會被阻擋。

本文記錄完整的原因分析、所有嘗試過的失敗方法,以及最終如何透過 CloakBrowser 成功突破。

為什麼無法直接取得 Perplexity 內容?

根本原因:瀏覽器指紋偵測

Perplexity 使用的保護機制不是傳統的「封 IP」或「輸入驗證碼」,而是瀏覽器指紋偵測(Browser Fingerprinting)。

當請求抵達 Cloudflare 時,會檢測以下項目:

  • navigator.webdriver 是否為 true(自動化框架標記)
  • navigator.plugins.length 是否符合真實瀏覽器
  • window.chrome 物件是否存在
  • WebGL / Canvas / Audio 指紋是否正常
  • WebRTC 是否洩漏真實 IP
  • TLS 指紋(JA3/JA4)是否為常見瀏覽器

只要有任何一項不符,Cloudflare 就判定為機器人,發回 JS Challenge 頁面。

常見工具為何全部失效?

嘗試方法失敗原因
curl + 多種 User-Agent無法執行 JavaScript,只能拿到 Challenge 頁 HTML
web_fetch / cloudscraper403,cloudscraper 對新版 Cloudflare 失效
Playwright(標準版)navigator.webdriver=true,立即被偵測
Playwright + stealth 參數仍有殘留信號,觸發指紋檢查
undetected-chromedriver連線錯誤,新版 Chrome 不相容
curl-impersonateTLS 指紋可能僥倖通過,但 JS Challenge 仍需瀏覽器執行
Xvfb + patchright底層同樣缺乏完整指紋修補

這些方法的共同盲點是:都在試圖「欺騙」Cloudflare 的 JS 檢測層,而非真正讓檢測看到的都是正常值。

解決方案:CloakBrowser

工具介紹

CloakBrowser(GitHub: CloakHQ/CloakBrowser)是 目前唯一在 C++ 層級修改 Chromium 原始碼的指紋修補方案。不是設定調整,不是 JS 注入,而是49 個指紋 patch 直接編進二進位,包括:

  • navigator.webdriver → false
  • navigator.plugins.length → 真實 plugin 清單
  • window.chrome → 正常瀏覽器物件
  • WebGL / Canvas / Audio 指紋
  • Font / Screen / Hardware 指紋
  • WebRTC IP leak 防護
  • TLS 指紋(JA3/JA4/Akamai)
  • Automation signals(CDP detection)

安裝方式

pip install cloakbrowser

Python 使用範例

from cloakbrowser import launch
import time

browser = launch(headless=False)
page = browser.new_page()

page.goto(
    "https://www.perplexity.ai/search/你的分享連結",
    wait_until="domcontentloaded"
)

# 等待 Cloudflare 挑戰自動通過
page.wait_for_function(
    "() => !document.title.includes('Just a moment')",
    timeout=40000
)
time.sleep(3)  # 額外等待確保渲染完成

html = page.content()
browser.close()

print(f"成功取得內容,HTML 長度: {len(html)} bytes")

關鍵實務細節

  1. 使用 headful 模式(headless=False):相較 headless,headful 的指紋更難被偵測,搭配 Xvfb 虛擬顯示器即可在無頭伺服器上運行
  2. 等待 Challenge 完成:wait_for_function 監控標題是否離開「請稍候」頁,而非盲目 sleep
  3. 額外等待 3 秒:確保 Perplexity 的 SPA 完全渲染動態內容

失敗方法完整列表

方法結果瓶頸
curl + UA 輪換無法執行 JS
cloudscraperCloudflare 版本過新
Playwright 標準版webdriver flag
Playwright + stealth仍有殘留信號
undetected-chromedriverChrome 版本不相容
curl-impersonateJS Challenge 仍需瀏覽器
Xvfb + patchright指紋修補不完整
CloakBrowser✅ 成功C++ 層級完整修補

結論

突破 Cloudflare JS Challenge 的核心瓶頸不在於「如何繞過檢測」,而在於讓檢測看到的全部都是正常瀏覽器值。CloakBrowser 的價值在於它從 Chromium 原始碼層面解決了這個問題,而不是停留在 JS 層或參數層的半成品繞過。

對於需要自動化存取受保護網頁的系統,CloakBrowser 是目前已知最穩定、成功率最高的方案。

(本文同步發布於研究記錄 2026-04-12)

2026/3/24

ollama launch openclaw

使用 ollama launch 來安裝設定 ollama,是有比較方便一點。
ollama launch openclaw
他會問你問題,ollama server (local or remote), model name ..
設定好會啟動 gateway.
然後開 openclaw tui 讓你測試。

2026/3/7

在 openclaw 安裝 searxng skill

This guide explains how to manually install the searxng skill on an OpenClaw host and configure it to use a specific SearXNG server instance.

Step 1: Download the Skill Files

Since the skill is part of the official OpenClaw skills repository but may not be available directly via the clawhub slug for all versions, we clone the repository and extract the specific skill folder.

# Clone the skills repository
git clone --depth 1 https://github.com/openclaw/skills.git /tmp/openclaw-skills

# Create the local skill directory
mkdir -p ~/.openclaw/skills/searxng

# Copy the searxng skill files (from abk234's contribution)
cp -r /tmp/openclaw-skills/skills/abk234/searxng/* ~/.openclaw/skills/searxng/

Step 2: Configure the SearXNG Server URL

The skill needs to know where your SearXNG instance is running. This is done by adding an env section to your global openclaw.json configuration file.

Add the following block to your ~/.openclaw/openclaw.json file:

{
  "env": {
    "SEARXNG_URL": "http://192.168.145.70:8081"
  },
  ... (rest of your configuration)
}
Tip: If you don't have a JSON editor handy, you can use Python to inject the configuration:
python3 -c "import json; f=open('$HOME/.openclaw/openclaw.json', 'r'); d=json.load(f); d['env'] = {'SEARXNG_URL': 'http://192.168.145.70:8081'}; f=open('$HOME/.openclaw/openclaw.json', 'w'); json.dump(d, f, indent=2)"

Step 3: Install Python Dependencies

The SearXNG skill script (searxng.py) requires specific Python libraries to handle HTTP requests and terminal formatting.

# Ensure pip is installed and then install dependencies
pip3 install httpx rich

Step 4: Verify the Installation

Run the following command to ensure OpenClaw recognizes the new skill and marks it as ready.

openclaw skills list

You should see 📦 searxng in the list with a checkmark (✓) or "ready" status. You can also test it directly via the CLI:

SEARXNG_URL=http://192.168.145.70:8081 python3 ~/.openclaw/skills/searxng/scripts/searxng.py search "your search query"

Step 5: Refresh the Agent

If OpenClaw is already running, you may need to restart the gateway or simply tell your agent to "refresh skills" to begin using the new capability.


結果還是不行,是model 太笨還是 ?
一直找不到 searxng,及使用 openclaw skills list 看,searxng 都已經是 "enabled" 了。openclaw tui 還是不知道可以用它來 search

2026/3/2

openclaw (我的)安裝與使用

用raspberry pi 4 4G. Debian 13 Trixie, headless
16G SD card 不夠裝 OS + openclaw。

因為不想用任何要付錢的東西,盡量用local service,加上不熟習 skill, plugin .. etc,所以只好改 source code,
所以 clone 一份下來改: 相關的修改和設定寫在SETUP_GUIdE.md

網路上寫的 free search engine 都要錢了,所以只好用自己架的 SearXNG。
這個不在 support list,所以修改了source code,把 SearXNG 加入search provider

然後在config 就可以寫 searxng .
"tools": {
  "web": {
     "search": {
        "enable": true,
        "provider": "searxng",
        "searxng": {
           "baseUrl": "http://192.168.145.70:8081/Search"
         }
      }
   }
 },

model 用 ollama 的 gpt-oss:120b, 和 qwen3.5:35b 和 122b。
用 ollama 的話,defaultthinking 要關掉,不然會有 error。

用screen run openclaw gateway run 之後,服務就起來了。
因為不用systemd,所以用 screen 來run,好在退出時繼續執行。

之後就可以用 openclaw agent 送message,看 reply。

然後啟動 web ui。
default 只會 listen on 127.0.0.1
要改成 listen on 0.0.0.0 程式要改很多地方,可能author不願意讓人這樣用。
髓以就用 ssh tunnel 來存取好了。

web url 要加上 token, 在 config 可以找到,這大概也是怕人隨意就能 access。
開啟 web 後就能用 web chat 來叫它做事了。

除了 model 修改不能叫它做,其它設定都可以叫它自己做。

2026/2/20

setup pi as a wifi ap

新的 pi os lite 已經不用 wpa_supplicant,改用 nmcli 了。
#!/bin/bash

# Configuration Variables
SSID="pi4app"
PASSWORD="pi4password"
AP_IP="192.168.4.1/24"
CON_NAME="Hotspot"
WLAN_IF="wlan0"

# 1. Check for root privileges
if [ "$EUID" -ne 0 ]; then
  echo "Please run as root (use sudo)"
  exit 1
fi

# 2. Check if nmcli is installed
if ! command -v nmcli &> /dev/null; then
    echo "Error: nmcli is not installed. This script requires NetworkManager."
    exit 1
fi

echo "--- Configuring Wi-Fi Access Point: $SSID ---"

# 3. Remove existing connection with the same name if it exists
nmcli connection delete "$CON_NAME" 2>/dev/null

# 4. Create the Hotspot connection
# - type wifi: Specifies a Wi-Fi connection
# - mode ap: Sets it to Access Point mode
# - band a: Sets it to 5GHz (use 'bg' for 2.4GHz)
# - ipv4.method shared: Enables internet sharing/NAT
# - ipv4.addresses: Sets a custom subnet to avoid conflicts with eth0
nmcli connection add type wifi ifname "$WLAN_IF" con-name "$CON_NAME" autoconnect yes ssid "$SSID" \
    802-11-wireless.mode ap \
    802-11-wireless.band a \
    ipv4.method shared \
    ipv4.addresses "$AP_IP" \
    wifi-sec.key-mgmt wpa-psk \
    wifi-sec.psk "$PASSWORD"

# 5. Explicitly ensure autoconnect is set to yes
nmcli connection modify "$CON_NAME" connection.autoconnect yes

echo "--- Activating $CON_NAME ---"

# 6. Bring the connection up
nmcli connection up "$CON_NAME"

echo "--- Setup Complete ---"
echo "Autoconnect status: $(nmcli -f connection.autoconnect connection show "$CON_NAME" | grep autoconnect)"
nmcli device status
ip addr show "$WLAN_IF"

2026/2/16

手動修改 partition content, enable ssh , wifi for headless raspberry pi OS.

 在沒有顯示器與鍵盤的情況下(Headless 模式),可以透過在 SD 卡的 boot 分割區中建立或編輯特定檔案,來完成系統初始化設定。

適用版本:Raspberry Pi OS Trixie (2025-12-04 以後的映像檔)。此版本改用 cloud-init 取代舊版的 userconf.txt 方式。

1. 啟用 SSH 服務

在 boot 分割區的根目錄下,建立一個檔名為 ssh 的空白檔案(不含任何副檔名)。系統在開機過程中由 sshswitch.service 偵測到此檔案後,會自動啟動 OpenSSH 服務並移除該檔案。

touch /media/你的使用者/bootfs/ssh

注意:僅在 cloud-init 的 user-data 中設定 ssh_pwauth: true 是不夠的,該設定只控制 SSH 是否允許密碼登入,並不會啟動 SSH 服務本身。必須建立 ssh 檔案才能啟動服務。

2. 設定使用者帳號與密碼(cloud-init)

自 Trixie 版本起,Raspberry Pi OS 改用 cloud-init 進行首次開機設定。舊版的 userconf.txt 方式已不再支援。使用者帳號需透過 boot 分割區中的 user-data 檔案來設定。

首先產生加密密碼字串:

openssl passwd -6 "你的密碼"

編輯 boot 分割區中的 user-data 檔案,內容如下:

#cloud-config

hostname: raspberrypi

users:
- name: charles-chang
  gecos: Charles Chang
  groups: users,adm,dialout,audio,netdev,video,plugdev,cdrom,games,input,gpio,spi,i2c,render,sudo
  shell: /bin/bash
  lock_passwd: false
  passwd: 此處填入上面指令產生的加密密碼字串
  sudo: ALL=(ALL) NOPASSWD:ALL

ssh_pwauth: true

runcmd:
- nmcli radio wifi on

說明:最後的 runcmd 區段是用來在首次開機時啟用 WiFi radio,原因見下節說明。

3. 設定 WiFi 無線連線

編輯 boot 分割區中的 network-config 檔案。此檔案使用 netplan 格式(YAML),cloud-init 會在首次開機時讀取並套用。

network:
  version: 2

  ethernets:
    eth0:
      dhcp4: true
      optional: true

  wifis:
    wlan0:
      dhcp4: true
      optional: false
      access-points:
        你的WiFi名稱:
          password: "你的WiFi密碼"

注意 YAML 縮排:此檔案對空白字元敏感,請使用空格而非 Tab。

WiFi radio 預設為停用:Raspberry Pi OS Trixie 的 NetworkManager 預設將 WiFi radio 設為軟體停用狀態(WIFI: disabled)。即使 network-config 中已正確設定 WiFi 連線資訊,WiFi 仍無法連線。解決方法是在 user-data 的 runcmd 中加入 nmcli radio wifi on,讓系統在首次開機時自動啟用 WiFi radio。

4. 開機與連線

將設定完成的 SD 卡插入樹莓派後送電開機。Cloud-init 首次開機約需 2-3 分鐘完成設定。完成後即可透過 SSH 連線:

ssh charles-chang@樹莓派的IP位址

可透過路由器的 DHCP 用戶端清單查詢樹莓派的 IP 位址。若已在 user-data 中安裝 avahi-daemon,亦可使用:

ssh charles-chang@raspberrypi.local

5. 安全建議

首次登入後,建議立即執行 passwd 指令更換初始密碼,以確保系統安全性。

設定檔案總覽

檔案位置用途
sshboot 分割區根目錄啟用 SSH 服務(空白檔案)
user-databoot 分割區根目錄設定使用者帳號、密碼、runcmd
network-configboot 分割區根目錄設定 WiFi 和乙太網路連線
meta-databoot 分割區根目錄cloud-init 中繼資料(通常不需修改)


很麻煩的是 /etc/hosts 也被networkmanager, cloud-init 管理了。
/etc/hosts 有一段話:
# Your system has configured 'manage_etc_hosts' as True.
# As a result, if you wish for changes to this file to persist
# then you will need to either
# a.) make changes to the master file in /etc/cloud/templates/hosts.debian.tmpl
# b.) change or remove the value of 'manage_etc_hosts' in
#     /etc/cloud/cloud.cfg or cloud-config from user-data
#
結果去看 /etc/cloud/cloud.cfg 只有 update_etc_hosts
這個 /etc/cloud/templates/hosts.debian.tmpl 看起來就是原始的,被copy到 /etc/hosts 的檔案,修改這個也可以。

2026/1/30

install choco on windows (for ripgrep, for cursor cli agent)

要安裝 ripgrep 到windows,說要choco.. 依照這一篇才成功: 用 powershell安裝 choco
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
然後就可以用 choco 安裝..
choco install ripgrep
其實這是為了要裝 cursor 的 cli:
irm 'https://cursor.com/install?win32=true' | iex
ref:

2026/1/17

codex-cli 使用自己的 ollama server.

codex-cli 可以用 --oss 來指定它使用 localhost 的 ollama server。 (default model: gpt-oss:20b)
但是(0.87.0)因為它hard coding provider: ollama 就是用 localhost,所以在 ~/.codex/.config.toml 寫了 provider.ollama 的 base_url 也是沒效。
所以不能用 ollama 這個 名子做 provder,要用其他的。例如:
[model_providers.ollama-remote]
name = "ollama"
base_url = "http://192.168.145.70:11434/v1"
wire_api = "responses"
這樣,用 ollama-remote 作為 provider 的 name。

另外,設定 default provider 用 ollama-remote,然後 model 是 gpt-oss:20b 是
model_provider = "ollama-remote"
model = "gpt-oss:20b"
model_reasoning_effort = "medium"
oss_provider = "ollama-remote"
這樣,直接啟動 codex 後,就會用這個自己的 ollama server 的 gpt-oss:20b 作為 model 了。

ollama server 版本要大於0.13.4 才 support responses API



claude-code 要用 local 的 ollama 的話,用環境變數就可以了。
export ANTHROPIC_AUTH_TOKEN=ollama
export ANTHROPIC_BASE_URL=http://192.168.145.70:11434
然後在啟動的時候指定 model:
claude --model gpt-oss:120b
ollama 要新版的才有支援 claude 的 api 格式.

新版 claude-code 的 web search 功能會使用 HAIKU model,所以要額外宣告環境變數。
為了確保所有 model 都用 ollama,就都宣告:
MODEL="${1:-qwen3.5:5b}"

# Export the environment variables
export ANTHROPIC_DEFAULT_OPUS_MODEL="$MODEL"
export ANTHROPIC_DEFAULT_SONNET_MODEL="$MODEL"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="$MODEL"
export CLAUDE_CODE_SUBAGENT_MODEL="$MODEL"
其實這樣 web search 還是不能用,因為還要一個 search engine。

所以可能要像openclaw一樣,幫他寫一個 searXNG search 的 SKILL

2026/1/14

Problem and Solve : some sections are mssing when VLM ocr pdf file

 

Problem

When converting 5.1.3. Tutorial for Large Language Models.pdf to Markdown, shell script sections were missing in the output. These sections had light gray text on light gray background (low contrast).

Root Cause

The VLM prompt didn't explicitly instruct the model to look for low-contrast code blocks. While the VLM could recognize the text, recognition was inconsistent.

Changes Made

1. Enhanced VLM Prompt (
ollama_client.py
)

Added explicit instructions to detect low-contrast code:

  • "Pay special attention to CODE BLOCKS and SHELL COMMANDS that may appear in LIGHT GRAY BOXES"
  • "These low-contrast code sections are VERY IMPORTANT and MUST NOT be skipped"
  • Specific examples: $ bash script.sh$ ./compile.sh$ adb push

2. Added VLM Output Cleanup (
ollama_client.py
)

New 

_clean_vlm_output() method removes VLM thinking noise:

  • Patterns like "Wait, no...", "Let me think...", "So final Markdown:"
  • Markdown code block wrappers
  • Multiple consecutive blank lines

2026/1/10

web ui for vlm : live-vlm-webui

live-vlm-webui 是給 vlm 的 web 界面。
安裝就用 pip
pip install live-vlm-webui
然後啟動:
$ live-vlm-webui
2026-01-10 22:02:23,010 - live_vlm_webui.server - INFO - No model/API specified, auto-detecting local services...
2026-01-10 22:02:23,016 - live_vlm_webui.server - INFO - ✅ Auto-detected Ollama at http://localhost:11434/v1
2026-01-10 22:02:23,016 - live_vlm_webui.server - INFO -    Selected model: llama3.2-vision:latest
2026-01-10 22:02:23,047 - live_vlm_webui.server - INFO - Initialized VLM service:
2026-01-10 22:02:23,047 - live_vlm_webui.server - INFO -   Model: llama3.2-vision:latest
2026-01-10 22:02:23,047 - live_vlm_webui.server - INFO -   API: http://localhost:11434/v1 (Local)
2026-01-10 22:02:23,047 - live_vlm_webui.server - INFO -   Prompt: Describe what you see in this image in one sentence.
2026-01-10 22:02:23,047 - live_vlm_webui.server - INFO - Serving static files from: /home/charles-chang/livevlmwebui/venv/lib/python3.12/site-packages/live_vlm_webui/static/images
2026-01-10 22:02:23,047 - live_vlm_webui.server - INFO - Serving favicon files from: /home/charles-chang/livevlmwebui/venv/lib/python3.12/site-packages/live_vlm_webui/static/favicon
2026-01-10 22:02:23,048 - live_vlm_webui.server - INFO - SSL enabled - using HTTPS
2026-01-10 22:02:23,048 - live_vlm_webui.server - INFO - Starting server on 0.0.0.0:8090
2026-01-10 22:02:23,048 - live_vlm_webui.server - INFO - 
2026-01-10 22:02:23,048 - live_vlm_webui.server - INFO - ======================================================================
2026-01-10 22:02:23,048 - live_vlm_webui.server - INFO - Access the server at:
2026-01-10 22:02:23,048 - live_vlm_webui.server - INFO -   Local:   https://localhost:8090
2026-01-10 22:02:23,049 - live_vlm_webui.server - INFO -   Network: https://192.168.145.77:8090
2026-01-10 22:02:23,049 - live_vlm_webui.server - INFO -   Network: https://172.20.0.1:8090
...
2026-01-10 22:02:23,050 - live_vlm_webui.server - INFO -   Network: https://192.168.94.37:8090
2026-01-10 22:02:23,050 - live_vlm_webui.server - INFO - ======================================================================
2026-01-10 22:02:23,050 - live_vlm_webui.server - INFO - 
2026-01-10 22:02:23,050 - live_vlm_webui.server - INFO - Press Ctrl+C to stop
2026-01-10 22:02:23,069 - live_vlm_webui.gpu_monitor - INFO - Auto-detected NVIDIA GPU (NVML available)
2026-01-10 22:02:23,069 - live_vlm_webui.gpu_monitor - INFO - Detected system: ASUS EX-B760M-V5
2026-01-10 22:02:23,076 - live_vlm_webui.gpu_monitor - INFO - NVML initialized for GPU: NVIDIA TITAN RTX
2026-01-10 22:02:23,076 - live_vlm_webui.server - INFO - GPU monitor initialized
2026-01-10 22:02:23,076 - live_vlm_webui.server - INFO - GPU monitoring task started
2026-01-10 22:02:23,076 - live_vlm_webui.server - INFO - GPU monitoring loop started
======== Running on https://0.0.0.0:8090 ========
(Press CTRL+C to quit)
然後在browsser 上開啟 https://localhost:8090 就可以。
API BASE URL 填入 ollama 的或是 sglang .. 都可以。
然後選 vlm model,開啟 camera,就會開始依照 prompt 內容對 video 做敘述...

2026/1/8

Yolo-World, VLM use yolo as image encoder

因為想知道 VLM 的 image encoder 是否都是 ViT,有沒有 YOLO base,結果搜尋出 有使用 YOLO 作為 Image Encoder.

其中 YOLO-World 又有 demo,所以就在 DGX Spark 上 setup 起來試試看。
GB10 的Cuda 有些限制,所以做了些修改才能 run,所以記錄在 GB10_SETUP.md 上。

demo 是用 gradio,上傳照片,promt 寫要偵測的東西,result 就是 object bounding box.

2026/1/4

Raspberry Pi 4 NetworkManager Wi-Fi AC 設定

 說明如何在 Raspberry Pi 4 環境下,透過 NetworkManager 將無線熱點設定為 5GHz (802.11ac) 模式。

1. 環境確認

執行以下指令確認無線網卡支援 5GHz 頻段:

iw list

輸出內容若包含 Band 2 相關資訊,即代表硬體支援。

2. 修改熱點設定

假設既有的熱點連線名稱為 wifi-ap。若要切換至 AC 模式,必須指定頻段與頻道。

設定頻段

將 wifi.band 參數設定為 a,這代表強制使用 5GHz 頻段。

sudo nmcli connection modify wifi-ap wifi.band a

設定頻道

建議手動指定一組非 DFS 頻道 (例如 36, 40, 44, 48) 以加速啟動時間。

sudo nmcli connection modify wifi-ap wifi.channel 36

套用變更

重新啟動連線以生效設定:

sudo nmcli connection up wifi-ap

3. 驗證狀態

使用以下工具檢視目前的運作頻率與頻寬:

iw dev wlan0 info

正確範例輸出:

channel 36 (5180 MHz), width: 20 MHz

若顯示頻率為 5000 MHz 以上 (例如 5180 MHz),且用戶端連線速率超過 72 Mbps,即確認已運作於 802.11ac 模式。

4. 技術限制說明

使用 NetworkManager 建立熱點時,預設會鎖定頻寬為 20 MHz。雖然運作於 5GHz 頻段,但最高理論速率限制約為 86.6 Mbps。此設定是為了確保最高的裝置相容性與連線穩定度。


另外使用nmcli設定完,nmcli會自動更新 /etc/NetworkManager/system-connections/ 目錄下的 .nmconnection。 讓設定永久保存。

Antigravity ssh remote failed

ssh 到 raspberry pi 都會失敗,但是連到 GB10 是 OK的。
到 pi 的 ~/.antigravity 去看 log: 在 data/logs/20260104T124620/remoteagent.log 有 SIGILL
Language server killed with signal SIGILL
找到一個很像 language server 的 : ./bin/94...af/extensions/antigravity/bin/language_server_linux_arm
run run 看真的 fail
~/.antigravity-server $ ./bin/94f...6af/extensions/antigravity/bin/language_server_linux_arm --version
Illegal instruction
所以用 gdb 來看,用這些參數:
   * `-ex "run"`: Tells GDB to start the program immediately.
   * `-ex "x/i \$pc"`: This is the most important part.
       * x = Examine memory.
       * /i = Format the output as a CPU instruction.
       * $pc = Look at the Program Counter (the exact address where the CPU stopped because of the error).
   * `-ex "bt"`: Generates a Backtrace to show the function call stack leading up to the crash.
   * `--batch`: Runs GDB in non-interactive mode and exits once the commands are finished.
   * `--args`: Allows you to pass the binary path and any flags it needs (like --version).
結果
~/.antigravity-server $gdb -ex "run" -ex "x/i \$pc" -ex "bt" --batch --args ./bin/94...6af/extensions/antigravity/bin/language_server_linux_arm --version

[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1".

Program received signal SIGILL, Illegal instruction.
0x000000555d5426ac in ?? ()
=> 0x555d5426ac:	ldaddal	x8, x8, [x9]
#0  0x000000555d5426ac in ?? ()
#1  0x0000007ff7fc1cdc in ?? () from /lib/ld-linux-aarch64.so.1
#2  0x0000007ff7fd83a0 in ?? () from /lib/ld-linux-aarch64.so.1
所以是 ldaddal
查這個 instruction 要ARMv8.1 LSE, raspberry pi 4 是 ARMv8.0,所以沒辦法。
另外用 VSCode 測試,ssh 到 raspberry pi 是 OK 的,髓以猜是language server 重新 compile for armv8.0 應該就可以....
然後 .vscode 沒有 language_server... 這個東西,連 bin 都沒有...

Set default login account for headless raspberry os image.

 在沒有顯示器與鍵盤的情況下(Headless 模式),可以透過在 SD 卡的 boot 分割區中建立特定檔案,來完成系統初始化設定。

1. 啟用 SSH 服務

在 boot 分割區的根目錄下,建立一個檔名為 ssh 的空白檔案(不含任何副檔名)。系統在開機過程中偵測到此檔案後,會自動啟動 OpenSSH 服務並在完成後移除該檔案。

2. 設定使用者帳號與密碼

Raspberry Pi OS 更新後,預設不再提供預設的 pi 使用者。必須手動建立 userconf.txt 檔案來定義使用者資訊。

在 boot 分割區根目錄下建立名為 userconf.txt 的純文字檔案,其內容格式如下:

使用者名稱:加密後的密碼字串

產生加密密碼字串的參考指令(以 Linux 系統為例):

echo -n "你的密碼" | openssl passwd -6 -stdin

範例檔案內容:

charles-chang:$6$0k2ljc4fIQEb9rWU$fo9gJ10kBqy7dcrJqj41W3ENmKVcIBRzeazsRSAqtqadQodTZRETA5LsTjb3TfjyafIOuRfNE8QNNDFmwBrr11

3. 網路連線與遠端存取

將設定完成的 SD 卡插入樹莓派並連接乙太網路線後送電開機。待系統啟動完成後,即可透過 SSH 客戶端連線。連線指令如下:

ssh charles-chang@raspberrypi.local

或者使用分享器分配給樹莓派的 IP 地址進行連線。

4. 安全建議

首次登入後,建議立即執行 passwd 指令更換初始密碼,以確保系統安全性。