openpnp – SlotSchultzFeeder source code bugfix

Article directory

    • openpnp – SlotSchultzFeeder source code bugfix
    • Overview
    • notes
    • openpnp source code debugging environment
    • Troubleshooting ideas
    • Open a git branch
    • Issues found – 1
    • Issues found – 2
    • Issues found – 3
    • Logical corrections to address the above issues
    • 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
    • Remark
    • END

openpnp – SlotSchultzFeeder source code bugfix

Overview

The feeder connected to my openpnp device is a second-hand feeder from Siemens, and I use the SlotSchultzFeeder provided by openpnp.

Discovered a problem (bug) with the original openpnp:
When connecting to multiple Siemens feeders, because you need to adjust the feeder parameters (or just want to confirm the parameters), when switching to different feeders, there is a high probability that an error message will pop up.
There are many types of error items (up to 4 types: the feeder ID cannot be obtained, the feeder feeding number cannot be obtained, the step size cannot be obtained, and the feeder status cannot be obtained).

If you do not use openpnp but use the serial port assistant, how to send commands to the mage2560 control baseboard? Feida can control it normally and return packets normally.

In fact, this problem has been discovered long ago, because at that time it was not the stage to use Siemens second-hand feeders on a large scale. As long as communication can be achieved, it means that the feeders are normal. I did not want to solve it.
Now I have to install all Siemens second-hand feeders. If error messages pop up from time to time, I can’t afford manual intervention. (I can’t stand this kind of harassment. Isn’t this being tricked by openpnp and feeder equipment?)
This problem must be solved now.

I began to suspect that there was a problem with the communication processing of the mega2560 control board, so I checked. There was indeed a problem.
But it’s not a problem with the programming logic, but a problem with the arduino library.
The Arduino library of mega2560 cannot handle multiple consecutive (within 100ms) serial port commands quickly and correctly.
This may not be a problem with the Arduino library. No matter which MCU, the packet interval speed sent by the serial port is less than the total time of the MCU processing a single serial port command, there is no guarantee for every command. All handled correctly.

After observing the openpnp log, I found that openpnp will continuously send 2 serial port commands to the Feida control board at the same time (within 10ms~20ms). No MCU can handle this.
I tried it with the serial port assistant and sent commands in a loop. As long as the sending interval was >200ms, there was no error in the Feida control board. That’s right. You can’t send commands to the MCU wildly.

This requires changing the openpnp source code. Find the place where 2 commands are sent continuously at the same time. Let’s do a simple process. Before each command is sent, there needs to be an interval of at least 250ms. The interval is provided by Java. Use sleep to handle it.

I’m not familiar with Java and I don’t want to find trouble. This time I was forced by openpnp to have no choice. I had to do it myself to get something to eat.
Fortunately, it is for maintenance. We can find out the cause of the bug and change the logic.

Notes

openpnp source code debugging environment

Experiments + notes (openpnp – software debugging environment construction) have been done for this environment for a long time. The local environment has not been touched yet. You can directly experiment according to the notes and have fun. It is so clever. I have already anticipated the day when the openpnp source code will be changed.

Troubleshooting ideas

According to the logs and error prompts, use the string search method + breakpoint method in the project. When an error is reported, it stops at the breakpoint, which means you have found the right place.
The IDE I use is , which is very easy to use.

Open git branch

The openpnp release version I am using now is openpnp-dev-2022-0801
The git url of openpnp dev code is: https://github.com/openpnp/openpnp.git, first move it out to local.
The latest code date is 2023/3/15
The corresponding installation package of openpnp-dev-2022-0801 is OpenPnP-windows-x64-develop_2022-08-01_18-07-09.2a36a8d.exe
The morning of 2022-8-1 was found in the git record, which was the final implementation code of 2022-8-1. A local branch was made at the last code submitted on 2022-8-1, named openpnp_dev_2022_0801
Make your own changes on this branch.

Questions found – 1

The sendcommand function of openpnp only has the parameters of the packet return timeout, but not the sleep time before sending. This causes the sender to send a command and it will be executed immediately. If multiple commands are executed continuously, the lower computer will not be able to process it. Come, the host computer will also process errors (because the reception is asynchronous processing, and the return packet format cannot distinguish which package it is, it will mistakenly treat the command return package that is not its own as its own, so that the command obtained from the return package The value target is wrong, e.g. the feeder parameter is filled in the wrong position).

Questions found – 2

There is indeed a place where orders are issued continuously.
When switching feeder entries, the four data on the feeder interface (feeder ID, feeder feeding number, step size, feeder status) are not saved and displayed, but when switching to the new feeder entry, Get the data immediately.
This results in that when switching to a different feeder barcode in the feeder list, there is a high probability that the mega2560 cannot handle it, resulting in no packet return, or an error in the packet return (e.g. the sent command is not fully recognized, and it is probably overwritten) , because the buffer is only 64 bytes, and there are 4 command input buffers. In fact, only one buffer is enough. The main reason is that the command interval sent by the host computer is too short, which exceeds the communication processing speed of the slave computer (the slave computer The computer image2560 needs to convert the host computer commands into the actual communication instructions of the Siemens feeder, wait for the feeder to return the packet, and then convert it into a reply packet that the host computer can understand. These all take time), errors will always occur, and this cannot be run away , it’s just a matter of probability.

Questions found – 3

When using a Smoothie motherboard made by a different manufacturer, obviously the motherboard is good and the serial port is no problem, but sometimes it takes 2 to 3 times or more to connect to the motherboard. The worst case scenario is that the motherboard cannot be connected all the time (if the problem occurs) I’ve been there before, it all depends on luck when I can connect to the motherboard communication)
When preparing to send the connection command to the Smoothie motherboard, you must sleep before sending it to the motherboard.

Logical corrections to address the above issues

Just follow the git submission records, in no particular order.

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

driver.sendCommand(cmd, 5000, 300); // Parameter 3 was originally not available, now it is the sleep time before sending

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

sendGcode(command, timeout, 0); // If it is confirmed that only one serial port command is sent here, and no other serial port commands will be sent before and after the timing, the sleep time before sending can be set to 0. This needs to be changed to verify.
 // Functions related to sending commands are added with the parameters of sleep before sending.
    protected void sendGcode(String gCode, long timeout, long time_sleep_before_send) throws Exception {<!-- -->
        if (gCode == null) {<!-- -->
            return;
        }
        for (String command : gCode.split("\
")) {<!-- -->
            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);

// To implement sleep before sending, just use Thread.sleep to simply handle it.
        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

 @Override
    public 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

In this implementation, where four commands are sent in succession, a private function is encapsulated for ms delay.

 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) {<!-- -->
// Here is the function that comes when switching in the feeder list. It is mainly used to fill in the interface parameters. If it is a parameter in the feeder device, take it out from the feeder and fill in the interface.
// ...
        statusText = new JTextField();
        statusText.setColumns(50);
        panelActuator.add(statusText, "8, 20");

        if(Configuration.get().getMachine().isEnabled()){<!-- -->
            // The commands cannot be concurrent, and the lower computer cannot process them.

// The original implementation of openpnp sent 4 instructions to Feida in a row, which caused Feida to be unable to process it.
// After the correction, the sleep time is 300ms before executing the command. Looking at the log, it is estimated that 249ms is OK.
            my_delay_ms(300); // add by ls
            getIdActuatorAction.actionPerformed(null);

            my_delay_ms(300); // add by ls
            getFeedCountActuatorAction.actionPerformed(null);

            my_delay_ms(300); // add by ls
            pitchActuatorAction.actionPerformed(null);

            my_delay_ms(300); // add by ls
            statusActuatorAction.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 up
        Thread.sleep(connectWaitTimeMilliseconds);

        // Consume any startup messages
        try {<!-- -->
            while (!receiveResponses().isEmpty()) {<!-- -->

            }
        }
        catch (Exception e) {<!-- -->

        }

        // Disable the machine
        setEnabled(false);

        // Send startup Gcode
        // Added parameter 3 (sleep time before sending), it may be that connectThreads() conflicts with the sending command here, e.g. connectThreads() has not turned off the serial port or the like.
        // Otherwise, there is no way to explain why sometimes openpnp throws out that the serial port is occupied, or the link cannot be connected.
        sendGcode_Ex(getCommand(null, CommandType.CONNECT_COMMAND), 200);

        connected = true;
    }
 @Override
    public void setEnabled(boolean enabled) throws Exception {<!-- -->
        if (enabled & amp; & amp; !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); // When commands may be sent continuously, add sleep before sending.
            }
            else {<!-- -->
                try {<!-- -->
                    sendGcode_Ex(getCommand(null, CommandType.DISABLE_COMMAND), 200); // When commands may be sent continuously, add sleep before sending.
                    drainCommandQueue(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 & amp; & amp; !enabled) {<!-- -->
            if (isInSimulationMode() || !connectionKeepAlive) {<!-- -->
                disconnect();
            }
        }
        super.setEnabled(enabled);
    }
//Where the actuator is executed, it may be a combination of sending commands continuously, plus parameter values are sent after sleeping.
    @Override
    public 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 send
        SimulationModeMachine.simulateActuate(actuator, on, true);
    }

    @Override
    public 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 send
        SimulationModeMachine.simulateActuate(actuator, value, true);
    }

    @Override
    public 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
    }
 @Override
    public 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 & amp; & amp; 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 compatibility
                    Double 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-related commands are sent after sleeping.
 // The original implementation only added the return packet timeout parameter for the convenience of calling. Now a new parameter time_sleep_before_send is added.
    protected 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("\
")) {<!-- -->
            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

The modified program will be given a new version number to distinguish it from the official implementation.

public class Main {<!-- -->
    public static String getVersion() {<!-- -->
    \t
        String version = Main.class.getPackage().getImplementationVersion();
        if (version == null) {<!-- -->
            // I didn’t see clearly where getImplementationVersion() got the version information, so I wrote a temporary version number first.
            version = "INTERNAL BUILD - base 2022-8-1 last, ls 2023_1026_0608PM";
        }
        return version;
    }

Remarks

Use IEDA to run the program (debugging state, run state), and openpnp to control the device.
At this time, when switching between feeders in the added feeder list, there will be a freeze for about 1 second, and then the feeder information will be displayed normally.
This card that takes more than 1 second, if it is during automatic placement (e.g. when automatically picking up materials, if a feeder is changed to feed the material, the feeder parameters may be retrieved again. During the automatic placement process, A pop-up message indicating that the Feida ID cannot be obtained) is not noticeable at all.
The problem has been solved and no adverse effects have been found yet

The only thing left is to publish the modified program for your own use, out of the IDE environment. This experiment has been completed, and I am sure it can be easily published for your own use on other computers.
This will be recorded in the next note, and it has nothing to do with modifying the source code. It is also so that you can find notes based on keywords in the future, e.g. Search for notes containing the “package” keyword in the oepnpnp column, and you will be able to find out how to publish the openpnp program for your own use.

END