在3D打印的世界里,Klipper固件以其卓越的运动控制算法和灵活的扩展性赢得了众多创客和开发者的青睐。本文将带你深入探索如何构建一个基于TCP协议的Klipper远程控制系统,从Moonraker API调用到UNIX套接字通信原理,最终实现一个功能完备的打印监控与控制系统。
Klipper固件采用了一种独特的架构设计——将复杂的运动规划算法从微控制器转移到性能更强的单板计算机(如树莓派)上运行。这种设计不仅提升了打印质量,还为远程控制提供了天然的优势。
核心通信流程:
提示:Moonraker作为Klipper的Web API服务层,提供了丰富的RESTful接口,是我们实现远程控制的关键桥梁。
bash复制# Python 3.7+环境
sudo apt update
sudo apt install python3 python3-pip
# 必需Python库
pip3 install python-socketio requests json-rpc
确保printer.cfg中包含以下基本配置:
ini复制[webhooks]
[virtual_sdcard]
path: ~/gcode_files
[moonraker]
enable_object_processing: True
以下是核心的TCP服务端实现代码,负责桥接网络请求与Klipper通信:
python复制import socket
import json
import threading
from pathlib import Path
class KlipperBridge:
def __init__(self, host='0.0.0.0', port=8899):
self.host = host
self.port = port
self.uds_path = str(Path.home() / "printer_data" / "comms" / "klippy.sock")
self.running = False
def start_server(self):
"""启动TCP服务端"""
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.bind((self.host, self.port))
self.server_socket.listen(5)
self.running = True
print(f"Klipper Bridge running on {self.host}:{self.port}")
while self.running:
client, addr = self.server_socket.accept()
threading.Thread(target=self.handle_client, args=(client,)).start()
def handle_client(self, client):
"""处理客户端连接"""
with client:
while True:
try:
data = client.recv(4096).decode('utf-8').strip()
if not data:
break
response = self.process_command(data)
client.sendall(json.dumps(response).encode('utf-8'))
except (ConnectionResetError, json.JSONDecodeError) as e:
print(f"Client error: {str(e)}")
break
def process_command(self, command):
"""处理Klipper命令"""
try:
cmd = json.loads(command)
if not all(k in cmd for k in ['method', 'params']):
return {"error": "Invalid command format"}
# 连接Klipper UNIX套接字
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as klippy:
klippy.connect(self.uds_path)
klippy.sendall(json.dumps(cmd).encode('utf-8') + b"\x03")
response = klippy.recv(4096).decode('utf-8')
return {"result": response}
except Exception as e:
return {"error": str(e)}
Moonraker提供了丰富的API端点,以下是几个关键API的使用示例:
| API端点 | 功能描述 | 示例请求 |
|---|---|---|
/printer/info |
获取打印机基本信息 | {"method":"printer.info"} |
/printer/emergency_stop |
紧急停止打印机 | {"method":"printer.emergency_stop"} |
/printer/gcode/script |
执行G代码指令 | {"method":"printer.gcode.script", "params":{"script":"G28"}} |
/printer/objects/query |
查询打印机对象状态 | {"method":"printer.objects.query", "params":{"objects":{"toolhead":["position"]}}} |
实战技巧:
objects/query可以高效获取多个状态值\n分隔可提高效率基于上述服务端,我们可以开发各种功能的客户端应用。以下是几个典型应用场景的实现思路:
python复制def monitor_temperatures(host, port, interval=1):
"""实时监控温度"""
with socket.create_connection((host, port)) as sock:
while True:
cmd = {
"method": "printer.objects.query",
"params": {"objects": {"extruder": ["temperature"], "heater_bed": ["temperature"]}}
}
sock.sendall(json.dumps(cmd).encode('utf-8'))
data = json.loads(sock.recv(4096).decode('utf-8'))
extruder_temp = data['result']['status']['extruder']['temperature']
bed_temp = data['result']['status']['heater_bed']['temperature']
print(f"Extruder: {extruder_temp:.1f}°C | Bed: {bed_temp:.1f}°C")
time.sleep(interval)
python复制def track_print_progress(host, port):
"""追踪打印进度"""
with socket.create_connection((host, port)) as sock:
cmd = {
"method": "server.files.metadata",
"params": {"filename": "current_print.gcode"}
}
sock.sendall(json.dumps(cmd).encode('utf-8'))
metadata = json.loads(sock.recv(4096).decode('utf-8'))
total_layers = metadata['result']['total_layer']
while True:
cmd = {
"method": "printer.objects.query",
"params": {"objects": {"print_stats": ["info"]}}
}
sock.sendall(json.dumps(cmd).encode('utf-8'))
stats = json.loads(sock.recv(4096).decode('utf-8'))
current_layer = stats['result']['status']['print_stats']['info']['current_layer']
progress = (current_layer / total_layers) * 100
print(f"Progress: {progress:.1f}% | Layer: {current_layer}/{total_layers}")
time.sleep(5)
python复制def move_axis(host, port, axis, distance, speed=100):
"""控制打印机轴移动"""
cmd = {
"method": "printer.gcode.script",
"params": {"script": f"G91\nG1 {axis.upper()}{distance} F{speed*60}\nG90"}
}
with socket.create_connection((host, port)) as sock:
sock.sendall(json.dumps(cmd).encode('utf-8'))
response = json.loads(sock.recv(4096).decode('utf-8'))
if 'error' in response:
raise RuntimeError(response['error'])
python复制def authenticate(client):
token = client.recv(256).decode('utf-8')
if token != "YOUR_SECURE_API_KEY":
raise ConnectionAbortedError("Authentication failed")
python复制ALLOWED_COMMANDS = {
'G28', 'G1', 'G90', 'G91', 'M104', 'M106',
'M107', 'M140', 'M190', 'M84'
}
def validate_gcode(script):
lines = script.split('\n')
for line in lines:
cmd = line.split(';')[0].strip().upper()
if cmd and cmd[0] in ('G','M') and cmd not in ALLOWED_COMMANDS:
return False
return True
python复制from functools import lru_cache
@lru_cache(maxsize=32)
def get_printer_objects():
"""缓存常用对象查询结果"""
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as klippy:
klippy.connect(self.uds_path)
cmd = {"method": "printer.objects.list"}
klippy.sendall(json.dumps(cmd).encode('utf-8') + b"\x03")
return json.loads(klippy.recv(4096).decode('utf-8"))
将上述组件整合为一个完整的控制系统,建议采用如下项目结构:
code复制klipper_remote/
├── server/ # 服务端代码
│ ├── bridge.py # 主服务程序
│ └── config.py # 配置文件
├── client/ # 客户端示例
│ ├── cli.py # 命令行客户端
│ └── monitor.py # 监控客户端
├── docs/ # 文档
└── requirements.txt # 依赖列表
部署脚本示例:
bash复制#!/bin/bash
# 启动Klipper桥接服务
python3 -m server.bridge --host 0.0.0.0 --port 8899 --log-level INFO
在实际项目中,我曾遇到UNIX套接字连接不稳定的问题,最终通过添加重试机制和心跳检测解决了这个问题。对于需要长时间运行的监控任务,建议使用专业的消息队列(如ZeroMQ)替代原始TCP连接,可以获得更好的可靠性和性能。