当前位置: 首页 > backend >正文

2.Klipper开发篇:Klipper上位机源码分析

        从Klipper执行流程和指令传输路线理清Klipper上位机整个源码架构和运行逻辑,理解klipper是如何启动运行的?指令是从哪里获取,去哪里执行?

1.klipper服务启动

        Klipper启动最核心的服务是klipper.service,安装好klipper后,在/etc/systemd/system下有该服务的文件,该服务文件的[Service]段内容是(注意,下面~代表linux用户目录,实际样式不是这样):

[Service]
Type=simple
User=tronxy
RemainAfterExit=yes
WorkingDirectory=~/klipper
EnvironmentFile=~/printer_data/systemd/klipper.env
ExecStart=~/klippy-env/bin/python $KLIPPER_ARGS
Restart=always
RestartSec=10

        可见它实际上是执行~/printer_data/systemd/klipper.env中的命令,该命令是:

~/klipper/klippy/klippy.py /home/tronxy/printer_data/config/printer.cfg -I ~/printer_data/comms/klippy.serial -l ~/printer_data/logs/klippy.log -a ~/printer_data/comms/klippy.sock

        命令涉及到klippy.py文件,它是上位机主进程文件,printer.cfg是打印配置文件

命令行直接跟在klippy.py后面的是配置文件printer.cfg;

-I(大写i)指定虚拟终端,在klippy.py文件中解析时,默认为/tmp/printer,这里指~/printer_data/comms/klippy.serial ,可以直接向它发命令:

        echo "M114" >> ~/printer_data/comms/klippy.serial

        但只能通过浏览器或者KlipperScreen的控制台看到回显

-l(小写L)指定日志文件klippy.log

-a 指定API服务器的socket文件,这里指向~/printer_data/comms/klippy.sock

        它是一个Unix Socket用以和其它应用(如Moonraker)通信用的,可以编写一个Unix Socket客户端,向它向发送指令。

        klippy.py文件中的main函数会解析上面的命令行,读取printer.cfg配置文件,完成打印机各模块实例化,然后启动下位机串口通讯,等待响应操作请求。

2. 整体框架

        服务启动后,运行到klippy.py的main函数,执行流程如下:

        main()解析命令行 -> 命令行参数打包到start_args中 -> 创建反应器reactor -> 构造Printer对象 -> 执行Printer.run进入无限循环,main函数核心代码如下:

while 1:...main_reactor = reactor.Reactor(gc_checking=True)printer = Printer(main_reactor, bglogger, start_args)res = printer.run()...
        2.1 构造反应器reactor

        reactor是驱动整个klipper上位机运行的核心对象,它在reactor.py中被构造,这里有两种可能的reactor,源代码如下:

# Use the poll based reactor if it is available
try:select.pollReactor = PollReactor
except:Reactor = SelectReactor

        它可能是PollReactor类构造的,也可能是SelectReactor构造的,区别就是PollReactor采用poll库,SelectReactor采用select库,poll是select进化版。

        无论是poll还是select,它们都监控sockets,open files, 或者 pipes(所有带fileno()方法的文件句柄)何时变成readable 和writeable, 或者通信错误,并返回文件句柄和事件(fd,event)。通常用它来监控多个sockets或pipes或文件是否有可读的信息然后处理这些信息,poll对IO文件不限制数量,select限制数量,较新版本的python都使用PollReactor。

        2.2 反应器reactor的核心功能

        printer对象运行run函数进入无限循环,最终是调用Reactor._dispatch_loop函数,也就是这个函数完成了整个klipper运行的循环。该函数的while循环体里,主要执行两大核心功能:

        _check_timers:检查对应函数register_callback注册到reactor中的延时执行函数时间是否到了,哪个函数时间到了就执行它。相当于并行的执行注册进来的回调函数,主要负责klipper的extra下各模块的回调。

        _poll.poll(waittime):通过poll库机制检查对应函数register_fd注册到reactor中的文件句柄是否可读或可写,然后调用对应的读取或写出回调函数。主要负责打印文件的读写,虚拟终端文件的读写,websocket的读写(与moonraker通信)等。

        核心代码如下:

# Main loop 主循环def _dispatch_loop(self):...while self._process:timeout = self._check_timers(eventtime, busy)...res = self._poll.poll(int(math.ceil(timeout * 1000.)))...for fd, event in res:...if event & (select.POLLIN | select.POLLHUP):self._fds[fd].read_callback(eventtime)...if event & select.POLLOUT:self._fds[fd].write_callback(eventtime)...
        2.3 klipper整体如何运行

        klipper根据配置文件,启用指定的模块,大部分模块都是在extra文件夹下,如printer.cfg中如果配置了[verify_heater],则extra下verify_heater.py文件就被启用。

        具体实现:一开始构造Printer对象时,注册了一个函数_connect到reactor中,待执行reactor核心循环后,回调_connect函数,该函数一开始就_read_config()->load_object()根据config.getsection加载对应的模块。

        每个被启用的模块都会构造相应的对象,同时注册回调函数到reactor中,这样就可以在执行Printer.run后被调用,进而各个模块都被连起来执行了。

3. 指令源及指令执行流程

        klipper有三大指令源,分别是虚拟终端,网络指令,打印文件。

        3.1 虚拟终端

        klipper默认虚拟终端(串口)是/tmp/printer,实际运行时,使用-I(大写的i)指定这个文件,klipper.service指定为~/printer_data/comms/klippy.serial。

        该虚拟终端文件在klippy.py的main函数中被打开,文件句柄保存在start_args['gcode_fd']中,传给Printer对象。

        Printer构造时,通过add_early_printer_objects函数加载了gcode.py中的GCodeDispatch类对象,保存在printer的objects中,名为gcode;同时也加载了GCodeIO类对象,保存在printer的objects中,名为gcode_io。

        加载GCodeIO过程中注册'gcode_fd'文件句柄到反应器reactor,回调函数_process_data处理虚拟文件读取的数据,并解析出命令调用gcode._process_commands函数执行指令,核心代码如下:

# Support reading gcode from a pseudo-tty interface
class GCodeIO:def __init__(self, printer):self.printer = printer...self.fd = printer.get_start_args().get("gcode_fd")self.reactor = printer.get_reactor()...if self.is_fileinput and self.fd_handle is None:self.fd_handle = self.reactor.register_fd(self.fd,self._process_data)
        3.2 网络指令

        网络指令一般由显示屏/网页发出,指令传输流程如下:

        网页/显示屏指令->moonraker->printer_data/comms/klippy.sock->klipper/webhooks.py。

        webhooks.py中的WebHooks对象也是add_early_printer_objects加载到Printer中去的,加载过程中创建socket以及注册回调函数流程如下:
        ServerSocket类 -> 创建klippy.sock(路径由命令行-a指定)-> 注册register_fd函数_handle_accept到反应器reactor -> 函数_handle_accept监听到连接 -> 创建ClientConnection对象。
        ClientConnection类->注册socket文件句柄:process_received(接收)和_do_send(发送)到reactor中 -> process_received函数接收moonraker传进的指令 -> 解析指令,获取指令对应在WebHooks类中注册的回调函数 -> 执行函数 -> do_send反馈结果到moonraker。
        注意:WebHooks类在很多模块中都有一些注册它的函数,因此moonraker会因启用的配置不一样而可以被传进来的指令也不一样
        Gcode指令(method:gcode/script)->被GCodeHelper类注册到Webhooks中->执行_handle_script->调用gcode.py中GCodeDispatch类成员run_script->实际执行_process_commands函数。

class WebHooks:def __init__(self, printer):self.printer = printer...self.sconn = ServerSocket(self, printer)class ServerSocket:def __init__(self, webhooks, printer):self.printer = printerself.webhooks = webhooksself.reactor = printer.get_reactor()...#创建socketself.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)self.sock.setblocking(0)self.sock.bind(server_address)self.sock.listen(1)#注册到reactorself.fd_handle = self.reactor.register_fd(self.sock.fileno(), self._handle_accept)...
        3.3 打印文件

        printer.cfg配置[virtual_sdcard]启用模块klippy/extras/virtual_sdcard.py

        开始打印一个文件的流程如下:

        moonraker发送命令SDCARD_PRINT_FILE -> 执行virtual_sdcard.py的VirtualSD类成员cmd_SDCARD_PRINT_FILE -> 执行_load_file函数 -> 通过os.path定位到文件,io.open打开文件 -> 然后调用do_resume启动打印 -> 注册work_handler到reactor中 -> 不停的读取文件内容 -> 调用gcode.py中GCodeDispatch类成员run_script执行指令 -> 实际执行_process_commands函数。

4.指令执行过程

        4.1 指令传递过程

        GCodeDispatch类成员run_script -> 实际执行_process_commands函数 -> 解析命令(踢出;注释行,行号N,提取命令号gcode_handler,匹配已注册的命令,即通过函数register_command注册在成员变量gcode_handlers上的命令)-> 执行其回调函数。

handler = self.gcode_handlers.get(cmd, self.cmd_default)

handler(gcmd)

        4.2 G1指令

        G1是几乎所有移动指令(G2,G3孤形指令也会被转化为G1执行)的基本指令,打印文件中95%以上的指令都是G1指令,因此,了解G1指令如何执行,几乎可以全部了解整个运动指令的执行过程。

        G1指令在gcode_move.py中被注册的回调函数为cmd_G1,该函数解析命令传入的参数,获取移动的终点和速度,然后将参数传给ToolHead.move(),该函数会根据参数创建一个Move对象,然后将它加到(add_move)移动队列中,等待被执行。后续怎么执行一条移动指令,在另一篇文章中分析。

到此,整个klipper上位机核心功能分析完了,klipper的其它模块功能(比如温度控制,调平,复位等)可以到extra目录下找到相应的py模块文件,分析它的实现过程。

http://www.xdnf.cn/news/6482.html

相关文章:

  • 时源芯微|TSFE0806U-2L-900TF复合共模滤波器在USB端口保护
  • Python函数参数传递机制深度解析:值传递与引用传递的真相
  • 理解c++中关键字友元friend的作用
  • 盲盒:拆开未知的惊喜,收藏生活的仪式感
  • 现代生活中的创新健康养生之道
  • LLM笔记(二)LLM数据基础
  • 【C++】Module CPP:模块化编程 Demo
  • 【C#】Thread.Join()、异步等待和直接join
  • C++delete详解剖析
  • 工具类来生成蓝牙指令
  • Java 序列化(Serialization)
  • 奇妙协同效应,EtherNet IP与PROFINET网关优化半导体生产线
  • Git .gitattributes 文件用途详解
  • Baklib知识中台驱动智能服务新实践
  • ZCC6303x-60V/1.2MHz 高效率升压 LED 恒流驱动替代SY7301
  • 【图片识别工具】批量单据识别批量重命名,批量OCR识别图片文字并重命名,批量改名工具的使用步骤和注意事项
  • Modbus TCP转Profinet网关:数字化工厂异构网络融合的核心枢纽
  • pciutils-3.5.5-win64工具的使用方法
  • Java大师成长计划之第23天:Spring生态与微服务架构之服务发现与注册中心
  • 使用命令行拉取 Git 仓库
  • 数学复习笔记 9
  • 自学嵌入式 day 18 - 数据结构 1
  • 嵌软面试每日一阅----FreeRTOS
  • SpringBoot实现简单的API代理服务器
  • Sumsub 活体检测与人证对比 Java Demo
  • pytorch训练可视化工具---TensorBoard
  • Linux 防火墙 firewalld 实战配置教程!
  • 将.pt文件执行图像比对
  • Java详解RabbitMQ工作模式之发布订阅模式
  • 具备AI功能的银河麒麟桌面操作系统已正式上市