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.mzph.cn/news/121977.shtml

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

相关文章

Linux下自动挂载U盘或者USB移动硬盘

最近在折腾用树莓派(实际上是平替香橙派orangepi zero3)搭建共享文件服务器,有一个问题很重要,如何在系统启动时自动挂载USB移动硬盘。 1 使用/etc/fstab 最开始尝试了用/etc/fstab文件下增加:"/dev/sda1 /home/orangepi/s…

从入门到精通:深入了解CSS中的Grid网格布局技巧和应用!

🎬 江城开朗的豌豆:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 📝 个人网站 :《 江城开朗的豌豆🫛 》 ⛺️ 生活的理想,就是为了理想的生活 ! ​ 目录 ⭐ 专栏简介 📘 文章引言 一…

论文阅读——GPT3

来自论文:Language Models are Few-Shot Learners Arxiv:https://arxiv.org/abs/2005.14165v2 记录下一些概念等。,没有太多细节。 预训练LM尽管任务无关,但是要达到好的效果仍然需要在特定数据集或任务上微调。因此需要消除这个…

Pytorch代码入门学习之分类任务(一):搭建网络框架

目录 一、网络框架介绍 二、导包 三、定义卷积神经网络 3.1 代码展示 3.2 定义网络的目的 3.3 Pytorch搭建网络 四、测试网络效果 一、网络框架介绍 网络理解: 将32*32大小的灰度图片(下述的代码中输入为32*32大小的RGB彩色图片)&…

【QT开发(17)】2023-QT 5.14.2实现Android开发

1、简介 搭建Qt For Android开发环境需要安装的软件有: JAVA SDK (jdk 有apt install 安装) Android SDK Android NDKQT官网的介绍: Different Qt versions depend on different NDK versions, as listed below: Qt versionNDK…

十五、城市建成区时空扩张分析——风向玫瑰图制作

一、前言 风向玫瑰图(简称风玫瑰图)也叫风向频率玫瑰图,它是根据某一地区多年平均统计的各个风向的百分数值,并按一定比例绘制,一般多用8个或16个罗盘方位表示,由于形状酷似玫瑰花朵而得名。 玫瑰图上所表示风的吹向,是指从外部吹向地区中心的方向,各方向上按统计数值…

13. 机器学习 - 数据集的处理

文章目录 Training data splitNormalizationStandardizedONE-HOT补充:SOFTMAX 和 CROSS-ENTROPY Hi, 你好。我是茶桁。 上一节课,咱们讲解了『拟合』,了解了什么是过拟合,什么是欠拟合。也说过,如果大家以…

SK海力士:将成为引领人工智能时代的定制型半导体存储器公司

AI芯片是一种专门针对人工智能应用设计的芯片,能够高效地处理人工智能任务,如机器学习、深度学习等。AI芯片具有高运算速度、低功耗、便于集成等特点,是人工智能领域的重要发展方向之一。 目前,AI芯片主要分为GPU、FPGA和ASIC三种…

Spark On Hive原理和配置

目录 一、Spark On Hive原理 (1)为什么要让Spark On Hive? 二、MySQL安装配置(root用户) (1)安装MySQL (2)启动MySQL设置开机启动 (3)修改MySQL…

Spring Boot进阶(94):从入门到精通:Spring Boot和Prometheus监控系统的完美结合

📣前言 随着云原生技术的发展,监控和度量也成为了不可或缺的一部分。Prometheus 是一款最近比较流行的开源时间序列数据库,同时也是一种监控方案。它具有极其灵活的查询语言、自身的数据采集和存储机制以及易于集成的特点。而 Spring Boot 是…

Android-宝宝相册(第四次作业)

第四次作业-宝宝相册 题目 用Listview建立宝宝相册,相册内容及图片可自行设定,也可在资料文件中获取。给出模拟器仿真界面及代码截图。 (参考例4-8) 创建工程项目 创建名为baby的项目工程,最后的工程目录结构如下图所…

报错:SSL routines:ssl3_get_record:wrong version number

一、问题描述 前后端联调的时候,连接后端本地服务器,接口一直pending调不通,控制台还报以下错误: 立马随手搜索了一下解决方案,但是emmm,不符合前端的实际情况: 二、解决方法: 实际…

SpringCore完整学习教程5,入门级别

本章从第6章开始 6. JSON Spring Boot提供了三个JSON映射库的集成: Gson Jackson JSON-B Jackson是首选的和默认的库。 6.1. Jackson 为Jackson提供了自动配置,Jackson是spring-boot-starter-json的一部分。当Jackson在类路径上时,将自动配置Obj…

理解V3中的proxy和reflect

现有如下面试题 结合GeexCode和Gpt // 这个函数名为onWatch,接受三个参数obj、setBind和getlogger。 // obj是需要进行监视的对象。 // setBind是一个回调函数,用于在设置属性时进行绑定操作。 // getlogger是一个回调函数,用于在获取属性时…

【阅读和学习代码】VoxelNet

文章目录 将点特征 转换为 voxel 特征稀疏张量 到 稠密张量,反向索引参考博客 将点特征 转换为 voxel 特征 https://github.com/skyhehe123/VoxelNet-pytorch/blob/master/data/kitti.py 【Python】np.unique() 介绍与使用 self.T : # maxiumum numbe…

php简单后门实现及php连接数据库

php简单后门实现 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>easybackdoor</title>…

云服务器搭建Spark集群

文章目录 1. Local 模式1.1 安装local模式1.2 命令行工具1.3 提交本地应用 2. Standlone模式2.1 集群配置2.2 修改配置文件2.3 启动集群与停止集群2.4 提交应用到集群环境2.5 提交应用的参数详细说明2.6 配置历史服务2.7 配置高可用&#xff08;HA&#xff09; 3. Yarn模式&…

如何使用ffmpeg制作透明背景的视频

最近我们尝试在网页上叠加数字人讲解的功能&#xff0c;发现如果直接在网页上放一个矩形的数字人视频&#xff0c;效果会很差&#xff0c;首先是会遮挡很多画面的内容&#xff0c;其次就是不管使用任何任务背景&#xff0c;画面都和后面的网页不是很协调&#xff0c;如图所示&a…

提升技能,挑战自我——一站式在线题库小程序

在这个信息爆炸的时代&#xff0c;我们总是在寻找一种方式&#xff0c;让自己在众多的知识海洋中快速提升技能&#xff0c;挑战自我。今天&#xff0c;我要向大家推荐一款全新的在线题库小程序KD蝌蚪阿坤&#xff0c;它将帮助你实现这个目标。 KD蝌蚪阿坤是一款全面的在线题库…

5 个编写高效 Makefile 文件的最佳实践

在软件开发过程中&#xff0c;Makefile是一个非常重要的工具&#xff0c;它可以帮助我们自动化构建、编译、测试和部署。然而&#xff0c;编写高效的Makefile文件并不是一件容易的事情。在本文中&#xff0c;我们将讨论如何编写高效的Makefile文件&#xff0c;以提高我们的开发…