openpnp - SlotSchultzFeeder source code bugfix

文章目录

    • openpnp - SlotSchultzFeeder source code bugfix
    • 概述
    • 笔记
    • openpnp源码调试环境
    • 排查思路
    • 开git分支
    • 查到的问题 - 1
    • 查到的问题 - 2
    • 查到的问题 - 3
    • 针对以上问题进行的逻辑修正
    • D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\machine\reference\driver\wizards\GcodeDriverConsole.java
    • D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\machine\reference\driver\GcodeDriver.java
    • D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\machine\reference\driver\GcodeAsyncDriver.java
    • D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\machine\reference\feeder\wizards\SlotSchultzFeederConfigurationWizard.java
    • D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\machine\reference\driver\GcodeDriver.java
    • D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\Main.java
    • 备注
    • END

openpnp - SlotSchultzFeeder source code bugfix

概述

我的openpnp设备接入的飞达是西门子二手飞达, 用openpnp提供的SlotSchultzFeeder.

发现原版openpnp有个问题(bug):
接入多个西门子飞达时, 因为要调整飞达参数(或仅仅就想确认一下参数), 切换到不同飞达时, 大概率会弹框报错.
报错的项目有多种(最多4种: 飞达ID取不到, 飞达送料数取不到, 步长取不到, 飞达状态取不到).

如果不使用openpnp, 而使用串口助手, 怎么发指令给mage2560控制底板, 飞达都可以正常控制, 回包都正常.

其实这个问题早发现了, 因为当时没到要大规模使用西门子二手飞达这步, 只要能通讯, 就说明飞达正常. 也没想去解决.
现在要将西门子二手飞达都挂上了, 要是时不时弹框报错, 那手工干预不起啊(这种骚扰顶不住, 这不成了被openpnp和飞达设备耍了么?)
现在必须要解决这个问题.

开始怀疑是mega2560控制板通讯处理有问题, 查了一下. 确实有问题.
但是不是编程逻辑的问题, 而是arduino库的问题.
mega2560的arduino库, 无法做到能快速正确的处理多个连续(100ms之内)的串口命令.
这可能也不是arduino库的问题, 随便哪个MCU, 串口发送的包间隔速度小于MCU的处理单条串口命令的总时间, 都不能保证每一个命令都处理正确.

观察了一下openpnp的日志, 发现openpnp会在同一时间(10ms~20ms之内), 连续发送2条串口命令给飞达控制板. 这哪个MCU也扛不住啊.
用串口助手试了一下, 循环发送命令, 只要发送间隔>200ms, 没有一次会让飞达控制板出错. 这就对了, 不能狂发命令给MCU啊.

这就需要改openpnp源码了, 将在同一时间连续不停的发送2条命令的地方找到, 简单处理一下, 在每条命令发送之前, 都至少需要间隔250ms以上才行. 间隔就用java自带的sleep处理一下.

对java不熟, 不想没事找事. 这次被openpnp逼的实在没招了. 非得自己动手才有吃的.
还好是维护性质的, 查引起bug的原因, 改点逻辑, 这个咱能搞.

笔记

openpnp源码调试环境

这个环境早就做了实验+笔记(openpnp - 软件调试环境搭建), 在本地的环境还没动, 可以直接照着笔记实验, 开心. 真机智, 早就预料到了会有改openpnp源码那一天.

排查思路

根据日志和报错提示, 在工程中用字符串搜索大法 + 断点法. 让报错时, 停在断点上, 那就说明找对了地方.
用的IDE是在这里插入图片描述, 挺好用的.

开git分支

我现在用的openpnp发布版本是 openpnp-dev-2022-0801
openpnp dev 代码的git url 为 : https://github.com/openpnp/openpnp.git, 先迁出到本地.
最新的代码日期为2023/3/15
openpnp-dev-2022-0801 对应的安装包为 OpenPnP-windows-x64-develop_2022-08-01_18-07-09.2a36a8d.exe
在git记录中查到2022-8-1上午, 是2022-8-1最后的实现代码. 在2022-8-1最后提交的代码处做了本地分支, 命名为openpnp_dev_2022_0801
在这个分支上做自己的修改.
在这里插入图片描述

查到的问题 - 1

openpnp的sendcommand函数, 参数只有回包超时时间, 而没有发送前需要sleep的时间. 这就导致发送者只要发送命令, 立刻就会被执行. 如果连续执行多条命令, 就会引起下位机处理不过来, 上位机也会处理错(因为接收是异步处理, 回包格式也不能区分出是哪个包, 就会错将不是自己的命令回包, 当作自己的, 这样从回包中取到的值的目标就错了, e.g. 飞达参数填错了位置).

查到的问题 - 2

确有连续狂发命令的地方.
在切换飞达条目时, 飞达界面上的4个数据(飞达ID, 飞达送料数, 步长, 飞达状态), 都不是存起来显示的, 而是切到新飞达条目, 就立刻取数据.
这就导致在飞达列表中切换到不同飞达条码时, 大概率会是mega2560处理不过来, 导致没回包, 或回包出错(e.g. 连发送的命令都没识别完整, 估计是被覆盖了, 因为缓冲区就64个字节, 有4个命令输入的缓冲区, 其实就一个缓冲区就够了, 主要是上位机发送的命令间隔太短了, 超出了下位机的通讯处理速度(下位机mage2560要将上位机命令转换为西门子飞达的实际通讯指令, 等飞达回包, 再转换为上位机能理解的回包, 这些都需要时间啊), 总是会出错, 这是跑不了的, 只是概率大小问题.

查到的问题 - 3

使用不同家做的冰沙主板时, 明明主板是好的, 串口也没问题, 但是有时要2~3次或者更多次才能和主板连接上. 最坏的情况是一直连不上主板(遇到过, 什么时候能连接上主板通讯, 都是靠运气)
准备给冰沙主板发连接命令时, 也要sleep之后, 才发给主板.

针对以上问题进行的逻辑修正

就按照git提交记录来, 不分先后.

D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\machine\reference\driver\wizards\GcodeDriverConsole.java

driver.sendCommand(cmd, 5000, 300); // 参数3原来是没有的, 现在为发送前的sleep时间

D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\machine\reference\driver\GcodeDriver.java

sendGcode(command, timeout, 0); // 如果确认这里只发一条, 且时机前后不会再发送其他串口指令, 发送前sleep时间就可以设置为0, 这个需要改完来验证.
   // 相关发送命令的函数都加上发送前sleep的参数protected void sendGcode(String gCode, long timeout, long time_sleep_before_send) throws Exception {if (gCode == null) {return;}for (String command : gCode.split("\n")) {command = command.trim();if (command.length() == 0) {continue;}sendCommand(command, timeout, time_sleep_before_send);}}public void sendCommand(String command) throws Exception {sendCommand(command, timeoutMilliseconds, 0);}public void sendCommand(String command, long timeout, long time_sleep_before_send) throws Exception {// An error may have popped up in the meantime. Check and bail on it, before sending the next command. bailOnError();if (command == null) {return;}Logger.debug("[{}] >> {}, {}, {}", getCommunications().getConnectionName(), command, timeout, time_sleep_before_send);// 发送前sleep的实现, 就用Thread.sleep简单处理一下.if (time_sleep_before_send > 0){Thread.sleep(time_sleep_before_send);}command = preProcessCommand(command);

D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\machine\reference\driver\GcodeAsyncDriver.java

    @Overridepublic void sendCommand(String command, long timeout, long time_sleep_before_send) throws Exception {if (waitedForCommands) {// We had a wait for commands and caller had the last chance to receive responses.waitedForCommands = false;// If the caller did not get them, clear them now.responseQueue.clear();}bailOnError();if (command == null) {return;}Logger.debug("{} commandQueue.offer({}, {})...", getCommunications().getConnectionName(), command, timeout);if (time_sleep_before_send > 0){Thread.sleep(time_sleep_before_send);}

D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\machine\reference\feeder\wizards\SlotSchultzFeederConfigurationWizard.java

这个实现中, 有连发4条命令的地方, 封装一个私有函数, 用来ms延时

    private void my_delay_ms(long ms){try {Thread.sleep(ms);}catch(InterruptedException e){// nothing, only catch// java: unreported exception java.lang.InterruptedException; must be caught or declared to be thrown}}
public SlotSchultzFeederConfigurationWizard(SlotSchultzFeeder feeder) {
// 这里是在飞达列表中切换会来的函数, 主要是填界面参数, 如果是在飞达设备中的参数, 从飞达中取出来, 再填入界面.
// ...statusText = new JTextField();statusText.setColumns(50);panelActuator.add(statusText, "8, 20");if(Configuration.get().getMachine().isEnabled()){// 命令不能并发, 下位机处理不过来.// openpnp原始实现连发了4条指令给飞达, 导致飞达处理不过来// 修正后, 在执行命令之前, 都sleep 300ms, 看日志, 249ms估计也可以.my_delay_ms(300); // add by lsgetIdActuatorAction.actionPerformed(null);my_delay_ms(300); // add by lsgetFeedCountActuatorAction.actionPerformed(null);my_delay_ms(300); // add by lspitchActuatorAction.actionPerformed(null);my_delay_ms(300); // add by lsstatusActuatorAction.actionPerformed(null);}for (Bank bank : SlotSchultzFeeder.getBanks()) {bankCb.addItem(bank);}// ...

D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\machine\reference\driver\GcodeDriver.java

    public synchronized void connect() throws Exception {disconnectRequested = false;getCommunications().connect();connected = false;connectThreads();// Wait a bit while the controller starts upThread.sleep(connectWaitTimeMilliseconds);// Consume any startup messagestry {while (!receiveResponses().isEmpty()) {}}catch (Exception e) {}// Disable the machinesetEnabled(false);// Send startup Gcode// 加了参数3(发送前sleep的时间), 可能是connectThreads()和这里的发送命令有冲突, e.g. connectThreads()还没有关掉串口之类的// 要不就没法解释清楚为啥有时openpnp抛出串口被占用, 或者链接不上的情况.sendGcode_Ex(getCommand(null, CommandType.CONNECT_COMMAND), 200);connected = true;}
    @Overridepublic void setEnabled(boolean enabled) throws Exception {if (enabled && !connected) {connect();}if (connected) {if (enabled) {// Assume a freshly re-enabled machine has no pending moves anymore.motionPending = false;sendGcode_Ex(getCommand(null, CommandType.ENABLE_COMMAND), 200); // 在可能会在连续发送命令的时机, 加上发送前的sleep}else {try {sendGcode_Ex(getCommand(null, CommandType.DISABLE_COMMAND), 200);// 在可能会在连续发送命令的时机, 加上发送前的sleepdrainCommandQueue(getTimeoutAtMachineSpeed());}catch (Exception e) {// When the connection is lost, we have IO errors. We should still be able to go on// disabling the machine.Logger.warn(e);}}}if (connected && !enabled) {if (isInSimulationMode() || !connectionKeepAlive) {disconnect();}}super.setEnabled(enabled);}
// actutor执行的地方, 都有可能是连续发送命令的组合, 都加上睡完发送参数值@Overridepublic void actuate(Actuator actuator, boolean on) throws Exception {String command = getCommand(actuator, CommandType.ACTUATE_BOOLEAN_COMMAND);command = substituteVariable(command, "Id", actuator.getId());command = substituteVariable(command, "Name", actuator.getName());if (actuator instanceof ReferenceActuator) {command = substituteVariable(command, "Index", ((ReferenceActuator)actuator).getIndex());}command = substituteVariable(command, "BooleanValue", on);command = substituteVariable(command, "True", on ? on : null);command = substituteVariable(command, "False", on ? null : on);sendGcode_Ex(command, 200); // param2, sleep then sendSimulationModeMachine.simulateActuate(actuator, on, true);}@Overridepublic void actuate(Actuator actuator, double value) throws Exception {String command = getCommand(actuator, CommandType.ACTUATE_DOUBLE_COMMAND);command = substituteVariable(command, "Id", actuator.getId());command = substituteVariable(command, "Name", actuator.getName());if (actuator instanceof ReferenceActuator) {command = substituteVariable(command, "Index", ((ReferenceActuator)actuator).getIndex());}command = substituteVariable(command, "DoubleValue", value);command = substituteVariable(command, "IntegerValue", (int) value);sendGcode_Ex(command, 200); // param2, sleep then sendSimulationModeMachine.simulateActuate(actuator, value, true);}@Overridepublic void actuate(Actuator actuator, String value) throws Exception {String command = getCommand(actuator, CommandType.ACTUATE_STRING_COMMAND);command = substituteVariable(command, "Id", actuator.getId());command = substituteVariable(command, "Name", actuator.getName());if (actuator instanceof ReferenceActuator) {command = substituteVariable(command, "Index", ((ReferenceActuator)actuator).getIndex());}command = substituteVariable(command, "StringValue", value);sendGcode_Ex(command, 200); // param2, sleep then send}
    @Overridepublic String actuatorRead(Actuator actuator, Object parameter) throws Exception {/** The logic here is a little complicated. This is the only driver method that is* not fire and forget. In this case, we need to know if the command was serviced or not* and throw an Exception if not.*/String command = getCommand(actuator, CommandType.ACTUATOR_READ_COMMAND);String regex = getCommand(actuator, CommandType.ACTUATOR_READ_REGEX);if (command != null && regex != null) {command = substituteVariable(command, "Id", actuator.getId());command = substituteVariable(command, "Name", actuator.getName());if (actuator instanceof ReferenceActuator) {command = substituteVariable(command, "Index", ((ReferenceActuator)actuator).getIndex());}if (parameter != null) {if (parameter instanceof Double) { // Backwards compatibilityDouble doubleParameter = (Double) parameter;command = substituteVariable(command, "DoubleValue", doubleParameter);command = substituteVariable(command, "IntegerValue", (int) doubleParameter.doubleValue());}command = substituteVariable(command, "Value", parameter);}sendGcode_Ex(command, 200); // actor相关的命令, 都加上睡后发送
	// 原始的实现为了方便调用, 只加了回包超时参数, 现在加入一个新参数time_sleep_before_sendprotected void sendGcode_Ex(String gCode, long time_sleep_before_send) throws Exception {sendGcode_Ex(gCode, timeoutMilliseconds, time_sleep_before_send);}protected void sendGcode_Ex(String gCode, long timeout, long time_sleep_before_send) throws Exception {if (gCode == null) {return;}for (String command : gCode.split("\n")) {command = command.trim();if (command.length() == 0) {continue;}sendCommand(command, timeout, time_sleep_before_send);}}public void sendCommand(String command) throws Exception {sendCommand(command, timeoutMilliseconds, 0);}public void sendCommand(String command, long timeout, long time_sleep_before_send) throws Exception {// An error may have popped up in the meantime. Check and bail on it, before sending the next command. bailOnError();if (command == null) {return;}Logger.debug("[{}] >> {}, {}, {}", getCommunications().getConnectionName(), command, timeout, time_sleep_before_send);if (command == "M610N3"){Logger.debug("bp");}if (time_sleep_before_send > 0){Thread.sleep(time_sleep_before_send);}// ...

D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\Main.java

改过的程序, 给个新版本号, 和官方实现区分开.

public class Main {public static String getVersion() {String version = Main.class.getPackage().getImplementationVersion();if (version == null) {// 没看清getImplementationVersion()从哪取的版本信息, 先硬写一个临时版本号version = "INTERNAL BUILD - base 2022-8-1 last, ls 2023_1026_0608PM";}return version;}

备注

用IEDA带着程序跑起来(调试状态, run状态), openpnp控制设备都好使.
此时, 在添加好的飞达列表中的飞达之间切换, 会卡大概1秒钟, 然后就会正常显示飞达信息.
这个1秒多时间的卡, 如果是自动贴片的时候(e.g. 自动取料时, 如果换了一个飞达供料, 也可能会重新取飞达参数, 遇到过在自动贴片过程中, 弹出取不到飞达ID的情况), 根本感觉不到.
问题已经解决了, 暂时未发现不良影响 😛

剩下的是事情就是将修改完的程序发布给自己用, 脱离IDE的环境. 这个也做完实验了, 确定可以简易发布给自己其他计算机用.
这个在下一篇笔记中记录, 和修改源码没关系. 也是为了自己以后好按照关键字来找笔记, e.g. 在oepnpnp栏目搜索包含"打包"关键字的笔记, 就能找到如何发布openpnp程序给自己用.

END

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/174038.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【计算机视觉】对极几何

文章目录 一、极线约束(Epipolar Constraint)二、相机标定过的情况三、相机没有标定过的情况四、八点算法(eight-point algorithm) 我的《计算机视觉》系列参考UC Berkeley的CS180课程,PPT可以在课程主页看到。 在上一…

Nokogiri库和OpenURI库使用HTTP做一个爬虫

Nokogiri和OpenURI是两个常用的Ruby库,用于编写爬虫程序。它们的主要功能如下: 1、Nokogiri:Nokogiri是一个强大的HTML和XML解析库,可以用于解析网页内容。它提供了一组简单易用的API,可以方便地遍历和操作HTML或XML文…

c++设计模式三:工厂模式

本文通过一个例子简单介绍简单工厂模式、工厂模式和抽象工厂模式。 1.简单工厂(静态) 假如我想换个手机,换什么手机呢?可以考虑苹果或者华为手机,那我们用简单工厂模式来实现这个功能: 我们关注的产品是手…

selenium判断元素可点击、可见、可选

1、判断元素是否可以点击 判断元素是否可以点击,WebElement对象调用is_enabled() is_enabled()方法返回一个布尔值,若可点击返回:True。若不可点击则返回:False from selenium import webdriver import time from selenium.web…

OpenCV C++ 图像处理实战 ——《缺陷检测》

OpenCV C++ 图像处理实战 ——《缺陷检测》 一、结果演示二、缺陷检测算法2.1、多元模板图像2.2、训练差异模型三、图像配准3.1 功能源码3.1 功能效果四、多元模板图像4.1 功能源码五、缺陷检测5.1 功能源码六、源码测试图像下载总结一、结果演示

ARP和DDOS攻击防御介绍

✍ 如何利用ARP漏洞进行攻击? ✍ 怎样有效地防御ARP攻击? ✍ 如何应对DDOS攻击? ---- ARP -- 地址解析协议 ---- 最简单的协议 pc和交换机通信后就会更新地址表: ARP: PC1要访问百度: 发送一个广播…

基于SSM的个性化美食推荐系统设计与实现

末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:采用JSP技术开发 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目&#x…

Websocket传递JWT令牌

在访问带有[Authorize]的方法的时候,需要前端通过自定义报文头的形式将JWT令牌传递给后端进行验证,否则是不能访问带有[Authorize]的方法。 [Authorize]是用于限制对web应用程序中某些操作或控制器的访问。当[授权]属性应用于操作或控制器时,…

vivo自研AI大模型即将问世,智能手机行业加速迈向AI时代

当前,以大模型为代表的人工智能技术已发展为新一轮科技革命和产业变革的重要驱动力量,被视作推动经济社会发展的关键增长极。 AI大模型潮起,千行百业走向百舸争流的AI创新应用期,前沿信息技术向手机、PC、车机等消费级终端加速渗…

Beyond Compare比较规则设置 Beyond Compare怎么对比表格

在对文件进行比较时,文件夹内的文件可能存在不同类型、不同后缀名、不同内容等差异,这些差异会影响具体的比较结果,因此需要我们对软件的比较规则进行一些设置。接下来就让我们一起来学习一下Beyond Compare比较规则设置,Beyond C…

Qt+JSON简单例子

QtJSON简单例子 QtJSONexample2参考 QtJSON #include "mainwindow.h" #include "ui_mainwindow.h" #include <QtDebug> #include <QJsonObject> #include <QJsonArray> #include <QJsonDocument> #include <QTextCodec> #i…

Linux C/C++ 实现网络流量分析(性能工具)

网络流量分析的原理基于对数据包的捕获、解析和统计分析&#xff0c;通过对网络流量的细致观察和分析&#xff0c;帮助管理员了解和优化网络的性能、提高网络安全性&#xff0c;并快速排查和解决网络故障和问题。 Linux中的网络流量常见类型 在Linux中&#xff0c;网络流量可以…

3.72 Command Buffer及URP概述

一、Command Buffer 1.概念 CommandBuffer携带一系列的渲染命令&#xff0c;依赖相机&#xff0c;用来拓展渲染管线的渲染效果。而且可以指定在相机渲染的某个点执行本身的拓展渲染。Command buffers也可以结合屏幕后期效果使用。 简单来说就是可以在渲染流程中插入一些自定…

R2R 的一些小tip

批次间控制器(Run-to-run Controller)&#xff0c;以应对高混合生产的挑战。将最优配方参数与各种工业特征相关联的模型是根据历史数据离线训练的。预测的最优配方参数在线用于调整工艺条件。 批次控制(R2R control)是一种先进的工艺控制技术&#xff0c;可在运行(如批次或晶圆…

招生报名缴费小程序开发笔记(上)

前期调研 1.数字化趋势&#xff1a; 随着社会的数字化转型&#xff0c;越来越多的教育机构倾向于采用数字工具来简化和优化他们的招生和报名过程。招生报名缴费小程序是应对这一趋势的一种解决方案&#xff0c;可以提供高效、方便、快速的在线招生渠道。2.用户需求&#xff1a…

进行商城的测试用例设计思路是什么?

进行商城的测试用例设计时&#xff0c;可以考虑以下思路&#xff1a; 1. 功能测试&#xff1a;测试商城的基本功能是否正常工作&#xff0c;包括用户注册、登录、浏览商品、搜索商品、添加商品到购物车、下单、支付等。 2. 数据验证测试&#xff1a;验证商城中的数据是否正确…

HarmonyOS 快速入门TypeScript

1.什么是TypeScript&#xff0c;它和JavaScript&#xff0c;ArkTs有什么区别 ArkTS是HarmonyOS优选的主力应用开发语言。它在TypeScript&#xff08;简称TS&#xff09;的基础上&#xff0c;匹配ArkUI框架&#xff0c;扩展了声明式UI、状态管理等相应的能力&#xff0c;让开发…

jenkins自动化操作步骤(gitblit)

1、登陆地址&#xff1a; http://xxxxxxxxx.org:xxxx/ admin/xxxx 2、创建任务 选择构建一个maven项目 3、配置 最多只保留一天一个任务 选择git仓库和账号密码 选择代码对应分支 build项&#xff1a; 1&#xff09;使用父项目的pom文件&#xff1a;k56-boot/pom.xml 2&…

《python语言程序设计》(2018版)第5章编程题 第41题第3次路过。总结一下。没有走完的路

这道题最大的需要就是能够进行两个数值的对比&#xff0c;同时还能让更多的数值依次进入到对比中。 这道题的解题版本 这个版本只是能统计出谁是最大数。但是无法统计最大数出现了多少次。 number "" count 0 data_number 0 while number ! 0:number eval(inpu…

Babylonjs学习笔记(五)——创建PBR材质

书接上回&#xff0c;这里讨论PBR材质&#xff01;&#xff01;&#xff01; // 创建天空盒/* */const createSkyBox (scene:Scene):void>{const envTex CubeTexture.CreateFromPrefilteredData(./env/environment.env,scene)scene.environmentTexture envTex;scene.cre…