K1SE 3D列印機的觸控顯示幕裝在本體外框右下角,仰角不能調整。如果3D列印機放在桌上,而你坐著操作它,螢幕的仰角剛剛好。但若是放在稍矮的地方,或者站著操作,觸控螢幕應該放在本體頂部。
K1SE的觸控螢幕採用40針B型排線(並列傳輸)連接控制板,Reddit論壇 “orlee008” 網友分別用400mm和500mm長的排線,延伸觸控螢幕。測試結果,用超過400mm長的排線連接,顯示器將無法啟動。
此外,40針排線的體積比較大,無法隱藏在本體外框內側,必須暴露在外,有礙觀瞻。
有人則是設計3D列印零件,把觸控螢幕的仰角調高20°,方便在低矮的桌面操作;這個是調高40°的版本,也有可調角度的設計。

另一個辦法是,準備第二個觸控螢幕或其他可監控3D列印機的設備,例如,舊手機、平板或其他具備觸控螢幕和網頁瀏覽器的裝置。
透過網頁瀏覽器監控3D列印機
現今3D列印機都具備聯網功能,像K1SE就是透過Wi-Fi連線操作,所以它本身就是一台物聯網實驗設備。這類3D列印機在開機時會啟動內建的HTTP伺服器,而列印軟體以及手機App,使用WebSocket協定與3D列印機連線、即時互動。
關於WebSocket,請參閱《超圖解ESP32深度實作》第9章「使用WebSocket即時連線監控聯網裝置」;《超圖解ESP32應用實作》第4章則用WebSocket連線ESP32開發板調控PID值,用圖表呈現即時溫度變化。
換句話說,只要能跑WebSocket協定,就能監控3D列印機。例如,執行JavaScript, Python, …等程式的電腦,或者ESP32 Arduino微控器。底下以創想三維K1SE為例,其他廠商型號的3D列印機的控制方式應該都差不多,因為幾乎所有3D列印機的操控軟體都是開源專案的客製化版本。
打開瀏覽器連線到3D列印機的IP位址(此例為192.168.1.204),就能開啟3D列印機HTTP伺服器提供的監控網頁(可切換顯示英文或簡體中文),如下圖。網頁左上方會顯示列印中的物件的縮圖,目前沒有在列印,因此呈現灰白立體方塊圖。
網頁右上角可呈現串流影像,我將它折疊起來,方便顯示底下的噴嘴和熱床的溫度變化動態圖表。中間左側的控制鈕可調整列印頭(toolhead)的X, Y, Z軸位置、開 / 關風扇和LED燈。
檢視3D印表機串流視訊來源
在了解如何編寫程式或用ESP32 Arduino監控3D列印機之前,筆者先製作一個簡易的測試網頁,呈現串流影像、即時噴嘴和熱床的溫度變化,以及LED燈開關。
先查看串流視訊的來源網址。在瀏覽器中按F12鍵(Mac版Chrome瀏覽器請按Command (⌘) + Option (⌥) + I鍵)開啟「開發人員工具」,點擊「網路」分頁的「篩選」列裡的「圖片」,可看到有個 “?action=stream” 影像來源持續在載入資料:
它就是串流視訊。點擊該網址可看到它的標頭,例如,等一下將會用到的請求網址 “http://列印機的IP位址/?action=stream”;點擊「預覽」則可預覽串流影像畫面。
在自製HTML網頁的內文區,設置如下的影像(img)標籤,即可呈現來自“http://192.168.1.204:8080” 網址的串流視訊:
<img src="http://192.168.1.204:8080/?action=stream" id="camera" alt="即時影像串流">
或者,先不指定影像標籤的來源(src),僅自訂唯一識別名稱“camera”:
<img id="camera" alt="即時影像串流">
再透過JavaScript動態設定它的影像來源也行:
<script>
const printerIP = "192.168.1.204"; // 3D列印機的IP位址
document.getElementById("camera").src = `http://${printerIP}:8080/?action=stream`;
</script>
查看WebSocket訊息內容
點擊「篩選」列的「通訊埠(socket)」,可見到由websocket.js發起的持續連線,請求網址是 “ws://列印機的IP位址:9999”。
點擊「名稱」欄裡的IP位址,再點擊右側面板的「訊息」,就能看到WebSocket訊息內容,從底下JSON格式訊息的成員名稱,可推測其內容是噴嘴與熱床溫度。
點擊3D列印機網頁上的控制鈕,例如LED燈的開關,它將送出開啟或關閉LED燈的訊息。
從捕捉到的畫面可得知,接收到的:
{"lightSw":0}
代表3D列印機回應「LED燈已關閉」;而它的前一則,傳送出去的訊息:
{"method":"set", "params":{"lightSw":0} }
代表設定(set)參數,其內容是「關閉LED燈」。
此外,上圖的訊息畫面的最後一則,是3D列印機持續傳出的「心跳(heart_beat)」訊息,其值是當前日期時間字串:
{"ModeCode":"heart_beat","msg":"2026-01-16T16:44:58.9427"}
「心跳」訊息用於告知WebSocket用戶端:3D列印機仍在運作,網路也沒有斷線。如果用戶端在幾秒(如:5或7秒)內沒有收到「心跳」訊息,就代表3D列印機可能關機、網路斷線或者當機了。
依照以上方法,調整3D列印機網頁上的各種操作,對照「訊息」面板裡的收、送訊息,即可推敲出每項控制和感測功能的識別名稱和數值格式。
附帶一提,許多網頁的JavaScript程式都是經過去除多餘的留白、刪除註解、簡化識別名稱等的min壓縮版,這個3D列印機的webSocket.js原始碼未經壓縮處理,因此可看到程式設計師留下的註解:
自製3D列印機即時資訊網頁
我們可以寫一個簡易版即時監控網頁,收 / 送上文解析的WebSocket訊息,網頁畫面如之前所示:
完整的HTML原始碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>K1SE列印機監控器</title>
<style>
body { font-family: "微軟正黑體", "黑體-繁", sans-serif;}
.status { margin: 10px 0; }
button { margin: 5px; }
img { border: 1px solid #ccc; max-width: 480px; }
</style>
</head>
<body>
<h1>K1SE列印機監控器</h1>
<!-- 即時影像串流 -->
<div>
<h2>即時影像</h2>
<img id="camera" alt="即時影像串流">
</div>
<!-- 狀態顯示 -->
<div class="status">噴嘴溫度:<span id="nozzle">--</span> °C</div>
<div class="status">熱床溫度:<span id="bed">--</span> °C</div>
<div class="status">列印進度:<span id="progress">--</span> %</div>
<!-- 控制按鈕 -->
<button onclick="turnOnLight()">開燈</button>
<button onclick="turnOffLight()">關燈</button>
<script>
const printerIP = "192.168.1.204"; // 3D列印機的IP位址
const ws = new WebSocket(`ws://${printerIP}:9999/`); // 建立WebSocket物件
const nozzleElm = document.getElementById("nozzle"); // 取得"nozzle"元素
const bedElm = document.getElementById("bed");
const progressElm= document.getElementById("progress");
// 設定串流影像來源
document.getElementById("camera").src = `http://${printerIP}:8080/?action=stream`;
ws.onopen = () => {
console.log("已連線到K1SE");
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.nozzleTemp) {
nozzleElm.textContent = parseFloat(data.nozzleTemp).toFixed(1);
}
if (data.bedTemp0) {
bedElm.textContent = parseFloat(data.bedTemp0).toFixed(1);
}
if (data.printProgress !== undefined) {
progressEl.textContent = data.printProgress;
}
} catch (e) {
console.log("非JSON訊息:", event.data);
}
};
ws.onclose = () => {
console.log("連線中斷…");
};
// 開燈
function turnOnLight() {
ws.send(JSON.stringify({method:"set", params:{lightSw:1}}));
}
// 關燈
function turnOffLight() {
ws.send(JSON.stringify({method:"set", params:{lightSw:0}}));
}
</script>
</body>
</html>
把上面的程式碼存成k1se.html,直接雙按,用瀏覽器開啟它,即可取得3D列印機的動態資訊。
我把這個網頁存入OpenWrt路由器內建HTTP伺服器的 “/www/” 路徑,也就是 ssh 連線至路由器後,執行底下命令新建 ”k1se.html” 檔,然後貼入HTML原始碼。
nano /www/k1se.html
如此,開啟 “http://192.168.1.1/k1se.html”,即可連線至3D列印機。
偵測3D列印機心跳(heart_beat)的網頁程式
修改上文的HTML原始碼,把兩個燈光控制鈕,改用一個核取方塊(checkbox),並且用CSS樣式把它的外觀改成滑鈕型式。
另外,加上偵測WebSocket的 “heart_beat”(心跳)訊息機制,若7秒內未收到心跳或其他訊息(如:噴嘴溫度),則顯示「關機或離線」。
為了切換顯示這兩個畫面,我把網頁內容分成兩個<div>區塊:
- <div id=”offline-ui”>:包含「關機或離線」訊息的畫面
- <div id=”main-ui”>:包含監控元素的畫面
完整的HTML原始碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>K1SE列印機監控器</title>
<style>
body { font-family: "微軟正黑體", "黑體-繁", sans-serif; }
.status { margin: 10px 0; }
button { margin: 5px; }
img { border: 1px solid #ccc; max-width: 480px; }
/* 預設隱藏 offline-ui */
#offline-ui { display: none; }
/* LED燈的切換鈕*/
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
vertical-align: middle;
}
/* 隱藏原本的核取方塊 */
.switch input { opacity: 0; width: 0; height: 0; }
/* 按鈕滑軌 */
.slider {
position: absolute;
cursor: pointer;
top: 0; left: 0; right: 0; bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 24px;
}
/* 滑動的小圓點 */
.slider:before {
position: absolute;
content: "";
height: 18px; width: 18px;
left: 3px; bottom: 3px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
/* 被勾選時的滑軌顏色 */
input:checked + .slider { background-color: #2196F3; }
/* 被勾選時圓點位移 */
input:checked + .slider:before {
transform: translateX(26px);
}
</style>
</head>
<body>
<div id="offline-ui">
<h1 style="color:#d9534f;">關機或離線</h1>
<button class="btn" onclick="location.reload()">重新嘗試連線</button>
</div>
<div id="main-ui">
<h1>K1SE控制面板</h1>
<!-- 即時影像串流 -->
<div>
<img id="camera" alt="即時影像串流">
</div>
<!-- 狀態顯示 -->
<div class="status">噴嘴溫度:<span id="nozzle">--</span> °C</div>
<div class="status">熱床溫度:<span id="bed">--</span> °C</div>
<div class="status">列印進度:<span id="progress">--</span> %</div>
<!-- 控制按鈕 -->
<div class="status">
燈光控制:
<label class="switch">
<input type="checkbox" id="light-toggle" onchange="handleToggle(this)">
<span class="slider"></span>
</label>
<span id="light-label">關</span>
</div>
</div>
<script>
const printerIP = "192.168.1.204";
const ws = new WebSocket(`ws://${printerIP}:9999/`); // 建立WebSocket物件
const nozzleElm = document.getElementById("nozzle"); // 取得 “nozzle” 元素
const bedElm = document.getElementById("bed");
const progressElm= document.getElementById("progress");
const lightElm = document.getElementById("light");
const offlineUI = document.getElementById("offline-ui");
const mainUI = document.getElementById("main-ui");
// 設定影像串流
document.getElementById("camera").src = `http://${printerIP}:8080/?action=stream`;
// 心跳檢查
let lastHeartbeat = Date.now();
// 每秒檢查心跳是否超過 7 秒未更新
setInterval(() => {
if (Date.now() - lastHeartbeat > 7000) {
// 偵測到超時,切換至離線畫面
if (mainUI.style.display !== "none") {
mainUI.style.display = "none";
offlineUI.style.display = "block";
}
} else {
// 恢復連線(如:lastHeartbeat更新了)則切回控制畫面
if (mainUI.style.display === "none") {
mainUI.style.display = "block";
offlineUI.style.display = "none";
var dummy = mainUI.offsetHeight;
}
}
}, 1000);
ws.onopen = () => {
// 已連線到K1SE…
mainUI.style.display = "block"; // 顯示控制介面
offlineUI.style.display = "none"; // 隱藏「關機離線」畫面
lastHeartbeat = Date.now(); // 初始化心跳時間
};
ws.onmessage = (event) => {
// 只要有訊息進來,就代表持續連線中…
lastHeartbeat = Date.now();
try {
const data = JSON.parse(event.data);
if (data.nozzleTemp) {
nozzleElm.textContent = parseFloat(data.nozzleTemp).toFixed(1);
}
if (data.bedTemp0) {
bedElm.textContent = parseFloat(data.bedTemp0).toFixed(1);
}
if (data.printProgress !== undefined) {
progressEl.textContent = data.printProgress;
}
// 修改原本解析 lightSw 的地方
if (data.lightSw !== undefined) {
const isOn = data.lightSw === 1;
document.getElementById("light-toggle").checked = isOn;
document.getElementById("light-label").textContent = isOn ? "開" : "關";
}
if (data.ModeCode === "heart_beat") {
lastHeartbeat = Date.now();
}
} catch (e) {
console.log("非JSON訊息:", event.data);
}
};
ws.onclose = () => {
console.log("連線中斷…");
};
ws.onerror = () => {
console.log("連線錯誤");
};
// 處理點擊開關的函式
function handleToggle(checkbox) {
const targetValue = checkbox.checked ? 1 : 0;
ws.send(JSON.stringify({
method: "set",
params: { lightSw: targetValue }
}));
}
</script>
</body>
</html>
