【3d打印】基于Klipper的3d打印远程上位机系统(Qt),klipper源码,通信解析,打印参数设置

3d打印远程上位机系统介绍

        此上位机是基于3d打印固件Klipper,使用pyqt+tcp协议,Klipper 是一款开源的 3D 打印机固件,其中Klipper提供了可以与外部通信的API,通过借助这些API,可以开发一个远程的3d打印系统,该系统可以控制x,y,z三轴任意距离的移动,风扇调速,轴速控制,摄像头画面获取和部分G代码命令。

klipper有提供技术文档概述 - Klipper 文档

githup开源地址https://github.com/Klipper3d/klipper

如果本文对你有所帮助,那能否支持一下老弟呢,嘻嘻🥰

 ✨✨个人主页 点击✨✨

klipper通信框架分析

🤠Klipper的网页端主要有以下几种

  • Mainsail:一个开源项目,主要使用 Vue.js 和 TypeScript 编程语言,旨在为使用 Klipper 固件的 3D 打印机提供流行的网页界面。具有响应式网页界面,可优化用于桌面、平板和移动设备;支持管理多台 3D 打印机;支持 12 种不同语言等众多功能。
  • Fluidd:也是一款免费、开源的 Klipper 网页界面,同样使用 Vue.js 等技术开发,用于管理 3D 打印机。提供响应式的用户界面,支持桌面、平板和移动设备,内置多种颜色主题,可从一个 Fluidd 安装中管理多个打印机,还能让用户根据喜好移动任何面板,实现个性化的界面布局。
  • Octoprint:它原本不完全算是专门为 Klipper 打造的网页界面,但也能与 Klipper 配合使用。有大量插件资源,适合已经习惯使用 Octoprint 且有较多插件依赖,或是追求功能全面、稳定可靠界面的用户。

🕵🏼就Fluidd而言,通信流程是Fluidd通过HTTP/Websocket协议连接运行在Klipper主机的web服务器Moonraker连接,moonraker再通过本地UNIX套接字与Klipper主机通信。

上述是浏览器通信,也就是HTTP协议的。

Klipper通信源码分析

Moonraker是一个用于外界客户端与Klipper主机通信的web服务器接口

Moonraker开源地址GitHub - Arksine/moonraker: Web API Server for Klipper

在klipper的源码路径为

/klipper-master/scripts/whconsole.py

whconsole.py中实现了如何通过本地UNIX套接字与Klipper通信

#!/usr/bin/env python2
# Test console for webhooks interface
#
# Copyright (C) 2020  Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, os, optparse, socket, fcntl, select, json, errno, time

# Set a file-descriptor as non-blocking
def set_nonblock(fd):
    fcntl.fcntl(fd, fcntl.F_SETFL
                , fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)

def webhook_socket_create(uds_filename):
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.setblocking(0)
    sys.stderr.write("Waiting for connect to %s\n" % (uds_filename,))
    while 1:
        try:
            sock.connect(uds_filename)
        except socket.error as e:
            if e.errno == errno.ECONNREFUSED:
                time.sleep(0.1)
                continue
            sys.stderr.write("Unable to connect socket %s [%d,%s]\n"
                             % (uds_filename, e.errno,
                                errno.errorcode[e.errno]))
            sys.exit(-1)
        break
    sys.stderr.write("Connection.\n")
    return sock

class KeyboardReader:
    def __init__(self, uds_filename):
        self.kbd_fd = sys.stdin.fileno()
        set_nonblock(self.kbd_fd)
        self.webhook_socket = webhook_socket_create(uds_filename)
        self.poll = select.poll()
        self.poll.register(sys.stdin, select.POLLIN | select.POLLHUP)
        self.poll.register(self.webhook_socket, select.POLLIN | select.POLLHUP)
        self.kbd_data = self.socket_data = b""
    def process_socket(self):
        data = self.webhook_socket.recv(4096)
        if not data:
            sys.stderr.write("Socket closed\n")
            sys.exit(0)
        parts = data.split(b'\x03')
        parts[0] = self.socket_data + parts[0]
        self.socket_data = parts.pop()
        for line in parts:
            sys.stdout.write("GOT: %s\n" % (line,))
    def process_kbd(self):
        data = os.read(self.kbd_fd, 4096)
        parts = data.split(b'\n')
        parts[0] = self.kbd_data + parts[0]
        self.kbd_data = parts.pop()
        for line in parts:
            line = line.strip()
            if not line or line.startswith(b'#'):
                continue
            try:
                m = json.loads(line)
            except:
                sys.stderr.write("ERROR: Unable to parse line\n")
                continue
            cm = json.dumps(m, separators=(',', ':'))
            sys.stdout.write("SEND: %s\n" % (cm,))
            self.webhook_socket.send(cm.encode() + b"\x03")
    def run(self):
        while 1:
            res = self.poll.poll(1000.)
            for fd, event in res:
                if fd == self.kbd_fd:
                    self.process_kbd()
                else:
                    self.process_socket()

def main():
    usage = "%prog [options] <socket filename>"
    opts = optparse.OptionParser(usage)
    options, args = opts.parse_args()
    if len(args) != 1:
        opts.error("Incorrect number of arguments")

    ml = KeyboardReader(args[0])
    ml.run()

if __name__ == '__main__':
    main()

🧐函数解析:

  • main()函数:

        前半段是用optparse库来进行接收命令行参数,该参数是用于UNIX套接字通信的文件,以.sock结尾,它是一种特殊类型的文件,用于在同一台计算机上的不同进程之间进行通信

ml = KeyboardReader(args[0])
    ml.run()

后半段是KeyboardReader类初始化并运行run()函数。

  • KeyboardReader类初始化:

 在__init__函数中有这样一行代码

self.webhook_socket = webhook_socket_create(uds_filename)

这是用于连接本地UNIX套接字的函数,其中uds_filename是传过来的UNIX套接字文件

  • webhook_socket_create()函数:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

这行代码用于创建一个 UNIX 域流式套接字对象

下面的while循环指的是与套接字来接,成功后返回一个用于通信的套接字对象sock。

到此KeyboardReader初始化完成。

  • run()函数:

其中self.process_kbd()是用于通信和解析JSON数据的函数

Klipper通信API协议分析

🌴klipper通信的协议是以“{JSON格式}”的数据,在Python中称为“字典”,且请求字典中必须包含一个”method”字段,其值应包含一个可用的Klipper端点”endpoint”名称字符串。

可用的endpoint有(部分)详细可看官方文档

  • info:

         “info” 用于从Klipper获取系统和版本信息。同时也被用来向Klipper提供客户端的版本信息。比如说

{"id": 123, "method": "info", "params": { "client_info": { "version": "v1"}}}

如果存在,“client_info”参数必须是一个字典,但该字典可能具有任意内容。鼓励客户端在首次连接到Klipper API服务器时提供客户端名称及其软件版本。

  • gcode/script:

        这个endpoint允许运行一系列的G代码命令。比如说:

{"id": 123, "method": "gcode/script", "params": {"script": "G90"}}
  • emergency_stop:

        "emergency_stop"端点用于指示 Klipper 过渡到 "shutdown"状态。它的行为类似于 G 代码         "M112 "命令。例如:

{"id": 123, "method": "emergency_stop"}
  • objects/list

        该端点查询可用打印机“对象”的列表,可以查询(通过“对象/查询”端点)。例如:

{“id”:123,“method”:“objects/list”}

可能返回: 

{"id": 123, "result": {"objects": ["webhooks", "configfile", "heaters", "gcode_move", "query_endstops", "idle_timeout", "toolhead", "extruder"]}}
  • objects/query

        这个endpoint允许从打印机对象中查询信息。比如说:

{"id": 123, "method": "objects/query", "params": {"objects": {"toolhead": ["position"], "webhooks": null}}}

 可能返回

{"id": 123, "result": {"status": {"webhooks": {"state": "ready", "state_message": "Printer is ready"}, "toolhead": {"position": [0.0, 0.0, 0.0, 0.0]}}, "eventtime": 3051555.377933684}}

使用TCP协议搭建应用程序/服务器与Klipper通信

        🔥通过上述的解释之后,我们知道了klipper的通信流程,我们可以使用tcp协议的客户端应用程序与moonraker通信,当然如果你不想使用moonraker服务器,你同样可以自己写一个服务器,如果只是功能实现的话,理论上来讲是比较容易的,因为前面说的已经很清楚了,本质就是UNIX+TCP通信。

下面就是一个简单的UNIX与TCP的服务器程序

import socket
import errno
import time
import sys
import json
from optparse import OptionParser
class server:
    #函数初始化
    def __init__(self):
        self.uds_filename = "/home/biqu/printer_data/comms/klippy.sock"
        parser = OptionParser()
        parser.add_option("-i", "--ip", dest="ip",
                        help="setting ip address for server",default="192.168.1.235")
        parser.add_option("-p", "--port", dest="port",
                        help="setting ip address for server", default=8899)

        (options, args) = parser.parse_args()

        if options.ip:         
            self.ip = options.ip
        if options.port:         
            self.port = options.port
        # print(self.ip,self.port)
        self.unixsocket = self.connect_unixserver(self.uds_filename)
        self.connect_tcpserver()
    #连接tcp服务器
    def connect_tcpserver(self):
        self.tcpser = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        # ip = input("请输入ip地址")
        self.tcpser.bind((self.ip,self.port))
        self.tcpser.listen(1)
        print("tcp server listening......")
        self.tcpsocket,addr = self.tcpser.accept()
        print("tcp server connect client successly",addr)
        while 1:
            bytedata = self.tcpsocket.recv(4096)
            if  bytedata:
                self.srtdata = bytedata.decode(encoding="utf-8")
                print(self.srtdata)
                self.process_kbd(self.srtdata)
            if not bytedata:
                print("server disconnect")
                break
        self.tcpser.close()
        self.unixsocket.close()
    #连接UNIX套接字
    def connect_unixserver(self,uds_filename):
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        print("unix server is connecting ......")
        sock.setblocking(0)
        while 1:
            try:
                sock.connect(uds_filename)
            except socket.error as e:
                if e.errno == errno.ECONNREFUSED:
                    time.sleep(0.1)
                    continue
                print("donot connect unix server %s [%d,%s]\n"
                                % (uds_filename, e.errno,
                                    errno.errorcode[e.errno]))
                sys.exit(-1)
            break
        print("unix server connect client successly.")
        return sock
    #将tcp份客户端的数据通过UNIX套接字传回klipper中
    def process_kbd(self,gcode):
        parts = gcode.split('\n')
        for line in parts:
            line = line.strip()
            if not line or line.startswith('#'):
                continue
            try:
                m = json.loads(line)
            except:
                sys.stderr.write("ERROR: Unable to parse line\n")
                continue
            cm = json.dumps(m, separators=(',', ':'))
            # self.unixsocket.send("%s\x03" % (cm))
            self.unixsocket.send(cm.encode()+b"\x03")

if __name__ == '__main__':
    ser = server()

其中UNIX套接字文件选择你klipper主机的printer_data目录下的.sock文件

self.uds_filename = "/home/xxx/printer_data/comms/klippy.sock"

IP和端口选择你运行klipper主机的ip地址和端口

运行

python2 server.py -i 192.168.242.34 -p 8899

注意最好用python2运行,python3运行会有问题。

而客户端只要按照服务器的IP和端口进行连接就可以了

如下

 def connect_server(self):
        self.client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self.client_socket.connect(192.168.242.34,8899)
        self.app.serveredit.setText("tcp服务器连接成功......") 

接下来只要按照klipper官方的通信协议发送就可以远程控制打印机了

G代码解析

🕵🏼G-code(G 指令)是一种用于控制计算机数控(CNC)机床、3D 打印机等设备的编程语言

也就是在3d模型经过切片软件之后得到的一个.gcode文件,这个文件是打印机可以识别的

常用命令

1. G0/G1 - 线性移动

  • G0: 快速移动(非打印移动)

  • G1: 线性移动(打印移动)

  • 示例:G1 X10 Y20 Z0.2 F1500
    解释:将打印头移动到X=10, Y=20, Z=0.2的位置,速度为1500 mm/min。

2. G28 - 归零

  • G28: 返回原点(Home)

  • 示例:G28 X Y
    解释:将X轴和Y轴归零。

3. G90/G91 - 坐标模式

  • G90: 绝对坐标模式

  • G91: 相对坐标模式

  • 示例:G90
    解释:切换到绝对坐标模式。

4. G92 - 设置当前位置

  • G92: 设置当前位置为指定值

  • 示例:G92 X0 Y0 Z0
    解释:将当前位置设置为X=0, Y=0, Z=0。

5. M104 - 设置挤出机温度

  • M104: 设置挤出机温度

  • 示例:M104 S200
    解释:将挤出机温度设置为200°C。

6. M109 - 等待挤出机温度

  • M109: 等待挤出机达到设定温度

  • 示例:M109 S200
    解释:等待挤出机温度达到200°C。

7. M140/M190 - 设置/等待热床温度

  • M140: 设置热床温度

  • M190: 等待热床达到设定温度

  • 示例:M140 S60
    解释:将热床温度设置为60°C。

8. M106/M107 - 控制风扇

  • M106: 开启风扇

  • M107: 关闭风扇

  • 示例:M106 S255
    解释:以最大速度(255)开启风扇。

9. M82/M83 - 挤出机模式

  • M82: 绝对挤出模式

  • M83: 相对挤出模式

  • 示例:M83
    解释:切换到相对挤出模式。

10. G29 - 自动调平

  • G29: 执行自动床面调平

  • 示例:G29
    解释:执行自动床面调平程序。

11. M84 - 禁用步进电机

  • M84: 禁用步进电机

  • 示例:M84
    解释:禁用所有步进电机。

12. M117 - 显示消息

  • M117: 在LCD屏幕上显示消息

  • 示例:M117 Printing...
    解释:在LCD屏幕上显示“Printing...”。

13. M300 - 播放声音

  • M300: 播放蜂鸣声

  • 示例:M300 S440 P1000
    解释:播放440Hz的声音,持续1000毫秒。

14. G2/G3 - 圆弧移动

  • G2: 顺时针圆弧移动

  • G3: 逆时针圆弧移动

  • 示例:G2 X10 Y10 I5 J0 F1500
    解释:顺时针移动到X=10, Y=10,圆心相对起点偏移I=5, J=0。

15. M600 - 换料

  • M600: 暂停打印并提示换料

  • 示例:M600
    解释:暂停打印并提示用户换料。

16. M501 - 加载设置

  • M501: 加载EEPROM中的设置

  • 示例:M501
    解释:加载EEPROM中的设置。

17. M500 - 保存设置

  • M500: 保存当前设置到EEPROM

  • 示例:M500
    解释:保存当前设置到EEPROM。

18. M201/M203 - 设置加速度和速度

  • M201: 设置加速度

  • M203: 设置最大速度

  • 示例:M201 X1000 Y1000
    解释:设置X轴和Y轴的加速度为1000 mm/s²。

19. M204 - 设置打印加速度

  • M204: 设置打印加速度

  • 示例:M204 P1000 T1500
    解释:设置打印加速度为1000 mm/s²,移动加速度为1500 mm/s²。

启庞Kp3s V1打印参数设置

上图是启庞的Kp3s V1高速打印机,因为自己买不起拓竹,所以就买了这个极具性价比的打印机

右图是用这台打印机打印的一条龙,打印的真不错😁

打印出一个好的零件,离不开好的参数配置。

切片软件是将模型转换为打印机可识别的文件的关键,我用的是

Orcaslicer官网下载OrcaSlicer - Powerful 3D Printing Slicer

接下来主要从两方面来介绍打印参数的设置

  1. 如何打好支撑,并且容易拆?
  2. 如何解决模型翘边问题?

问题一:打开Orcaslicer,左侧边栏选择“支撑”

  • 类型:有四种类型可选,默认是“树状自动”,若遇到仅需在特定位置添加支撑的可以选“树状”手动
  • 阈值角度:对悬垂角度低于阈值的生成支撑,默认35度
  • 首层扩展:支撑首层与热床的接触,数值越大,与热床接触越多,可以适当调大
  • 顶部z距离:支撑顶部和模型的间距,决定支撑是否好拆,可以上下0.05调整 
  • 底部z距离:支撑底部在模型表面时的z距离,可选择默认
  • 支撑/模型xy间距:支撑和模型之间的xy距离。

问题二::打开Orcaslicer,左侧边栏选择“其他”

找到brim

  • brim类型:选择仅外侧,
  • brim宽度:可大,越大与热床接触越多,越不容易翘边,
  • brim与模型之间的间隙:越大越容易拆掉,但与模型接触太少会起不到解决翘边的作用,越小约不容易拆掉

参考文章klipper源码分析之whconsole.py-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值