早期礦工攜帶金絲雀下井,鳥對毒氣敏感,倒斃即撤離信號。軟件項目同樣需要「高可視、即時、無需查郵箱」的構建狀態指示器。本文記錄如何利用 Sun SPOT (Small Programmable Object Technology) 構建無線構建監視器,集成 CruiseControl 持續集成系統。
| 組件 | 規格 |
| 主控 | 180MHz Atmel AT91RM9200 SoC |
| 存儲 | 4MB Flash + 512KB 偽靜態 RAM |
| 電源 | 內置鋰電池(3h 連續) / USB / 外接電源 |
| 傳感器 | 溫度、光照、三軸加速度計 |
| 輸出 | 8 個三色 LED、2 個按鈕 |
| 無線 | IEEE 802.15.4 (2.4GHz)、點對點/多跳 |
| JVM | Squawk (幾乎純 Java 實現、裸機運行、CLDC 1.1) |
系統架構
[CruiseControl 構建服務器]
│ (USB)
[基站 SPOT] ←→ 無線 802.15.4 → [遠程 SPOT (監視器)]
│
[CanaryHandler Java SE 應用]
- CruiseControl 輪詢 SVN,觸發 Maven 構建
- 構建階段變化 (RUNNING/SUCCESS/FAILED) → 調用
CanaryHandler
CanaryHandler 經基站 SPOT 發送無線命令
- 遠程 SPOT 運行
BuildCanary MIDlet,解析命令驅動 LED
關鍵代碼實現
1. CanaryHandler (構建服務器端)
public class CanaryHandler {
public static void main(String[] args) throws IOException {
// 解析參數: --running/--failed/--success --spot "0014.4F01.xxxx.yyyy" --port 100 --serial COM4
CommandLineParser parser = new PosixParser();
Options options = buildOptions();
CommandLine cmd = parser.parse(options, args);
String spotAddr = cmd.getOptionValue("spot");
String port = cmd.getOptionValue("port");
String connStr = "radiostream://" + spotAddr + ":" + port;
System.setProperty("SERIAL_PORT", cmd.getOptionValue("serial"));
StreamConnection conn = (StreamConnection) Connector.open(connStr);
DataOutputStream dos = conn.openDataOutputStream();
DataInputStream dis = conn.openDataInputStream();
String msg = cmd.hasOption("running") ? "RUN" :
cmd.hasOption("failed") ? "FAIL" : "SUCCESS";
dos.writeUTF(msg);
dos.flush();
System.out.println("SPOT 回應: " + dis.readUTF());
conn.close();
}
}
2. BuildCanary (遠程 SPOT MIDlet)
public class BuildCanary extends MIDlet {
private RadiostreamConnection conn;
private DataInputStream dis;
private DataOutputStream dos;
private Thread colourThread;
private SetColour setColour;
protected void startApp() { new Thread(this::updateBuildStatus).start(); }
private void updateBuildStatus() throws IOException {
// 就緒指示:全白
setColour(LEDColor.WHITE, "");
String baseAddr = getAppProperty("BaseStationAddress");
String port = getAppProperty("PortNumber");
conn = (RadiostreamConnection) Connector.open("radiostream://" + baseAddr + ":" + port);
dis = conn.openDataInputStream();
dos = conn.openDataOutputStream();
while (true) {
String status = dis.readUTF(); // 阻塞等待命令
if ("RUN".equals(status)) setColour(LEDColor.BLUE, status); // 藍色流光
else if ("SUCCESS".equals(status)) setColour(LEDColor.GREEN, status); // 綠色常亮
else if ("FAIL".equals(status)) setColour(LEDColor.RED, status); // 紅色閃爍
else throw new IllegalArgumentException("Unknown: " + status);
dos.writeUTF("Updated to " + status);
dos.flush();
}
}
private void setColour(LEDColor color, String status) {
if (colourThread != null) colourThread.interrupt();
setColour = new SetColour(color, status);
colourThread = new Thread(setColour);
colourThread.start();
}
// 內部類:驅動 8 個 LED
class SetColour implements Runnable {
private final ITriColorLED[] leds = EDemoBoard.getInstance().getLEDs();
private final LEDColor color;
private final String status;
public void run() {
try {
if ("RUN".equals(status)) {
while (true) { // 藍色流光
for (int i=0; i<8; i++) {
leds[i].setColor(color); leds[i].setOn();
Thread.sleep(200);
leds[i].setOff();
}
}
} else if ("FAIL".equals(status)) {
while (true) { // 紅色閃爍
for (int i=0; i<8; i++) { leds[i].setColor(color); leds[i].setOn(); }
Thread.sleep(250);
for (int i=0; i<8; i++) { leds[i].setOff(); }
Thread.sleep(250);
}
} else { // SUCCESS: 綠色常亮
for (int i=0; i<8; i++) { leds[i].setColor(color); leds[i].setOn(); }
}
} catch (InterruptedException e) { /* 線程切換顏色,正常退出 */ }
}
}
}
3. CruiseControl 配置集成 (config.xml)
<property name="javaExecutable" value="C:\jdk\bin\java.exe"/>
<property name="workingDir" value="C:\CanaryHandler\dist"/>
<property name="libPath" value="-Djava.library.path=C:\SunSPOT\sdk\lib"/>
<property name="commonArgs" value="${libPath} -jar ${workingDir}\CanaryHandler.jar
--spot "0014.4F01.0000.3A19" --port 100 --serial COM4"/>
<project name="ci-build">
<schedule interval="60">
<composite>
<exec command="${javaExecutable}" args="${commonArgs} --running"/> <!-- 構建開始 -->
<maven2 pomfile="pom.xml" goal="clean compile"/>
</composite>
</schedule>
<publishers>
<onsuccess><exec command="${javaExecutable} ${commonArgs} --success"/></onsuccess>
<onfailure><exec command="${javaExecutable} ${commonArgs} --failed"/></onfailure>
</publishers>
</project>
運行效果演示
| 構建階段 | LED 效果 | 視覺含義 |
| 初始化 | 全白 | 監視器就緒待命 |
| 構建中 (RUNNING) | 藍色流光 | 進度進行中,請勿提交衝突代碼 |
| 構建失敗 (FAILED) | 紅色閃爍 | 緊急:有人破壞構建,需立即修復 |
| 構建成功 (SUCCESS) | 綠色常亮 | 穩定:可以安全拉取/部署 |
技術難點與解決
- CLDC 限制:無反射、無序列化、無正則、無 Swing、集合僅 Vector/Hashtable/Enumeration → 代碼需精簡,避免標準庫依賴。
- 無線可靠性:
radiostream 協議提供可靠流式連接,類似 TCP,自動重傳。
- MIDlet 生命周期:
startApp() 啟動監聽線程,destroyApp() 釋放連接資源。
- 基站串口鎖定:Windows 下 COM 口唯一佔用,CruiseControl 串行執行避免衝突。
擴展想像空間
Sun SPOT 社區已實現:火箭遙測教學、樹林火災監測、體感遊戲控制器、農業環境感知網絡。構建監視器只是冰山一角——將軟件狀態映射到物理世界的可視/可觸反饋,是物聯網時代開發者的新邊疆。
總結
用約 200 行 Java 代碼 + 一個 CruiseControl 配置片段,構建了「極客桌面上的持續集成儀表盤」。SPOT 的開源性、Java 原生編程模型、豐富傳感器/执行器,讓軟件工程師能以極低門檻跨越到物理計算領域。下次構建失敗時,看一眼桌上的紅燈閃爍,比郵件通知快得多——這就是金絲雀的現代價值。
- 上一篇文章: Servlet 會話管理深度解析:HttpSession API、Cookie/URL 重寫機制與最佳實踐
- 下一篇文章: 輕量級數據管理分析平台實戰:JDBC + Applet + 動態 SQL 模板 + 客戶端可更新 ResultSet
Copyright ©
程式碼之家 All Rights Reserved