ZA6 Control GUI Creation

I am trying to create custom control/user interface for the ZA6 so that a user/operator can start and stop the robot and adjust some settings without having to mess about in PathPilot. I see that Qt5 is available, but I can’t quite figure out how to integrate this with the robot program. Can anyone provide any help or examples on how to do this?

I think the best way how to think about it to separate it into two halves (because that is how it is implemented):

  1. The GUI
  2. The robot program

The PathPilot Robot Interpreter executes the TRPL program and controls the robot (moves it, waits on input, communicates over network) and the PathPilot Robot UI shows the buttons, DRO and robot preview. From system perspective, these are completely separate processes.

When you load a new TRPL program into the Interpreter, for example guitest.py, the Robot UI will take a look if there is a file named guitest.qml. (From the extension, you can see that it must be a QtQuick based program written in QML.) If there is this .qml file, the moment you click on the Cycle start button, the Interpreter starts executing your program, but the robot UI instead of showing the program lines (as happens in standard mode) will load provided .qml file.

Of course, given that both parts are running in different processes, you need to somehow communicate between them. So far, how it was done is by using (hacking) the parameter system (set_param and get_param TRPL functions and the Config QML singleton) and then having a busyloop waiting on change of parameters.

In terms of code example it goes something like this:

from robot_command.rpl import *

set_units("mm", "deg", "second")

def main():
    sleep(2)
    user_message = get_param("command", None)
    set_param("command", None)
    if user_message:
        notify(user_message)

for the actual TRPL program and

import QtQuick 2.0
import QtQuick.Layouts 1.0
import QtQuick.Controls 2.0

import pathpilot.core 1.0

RowLayout{
    Button{
        text: "Hi"

        onClicked: {
            Config.user.custom.command = text
        }
    }
    Button {
        text:"Hello"

        onClicked: {
            Config.user.custom.command = text
        }
    }
}

for the UI part. Make sure to import the import pathpilot.core 1.0 and then use the Config.user.* property of the Config QML singleton. If you look at the list of ROS parameters, you should see:

$> rosparam list
...
/user_config/custom/command
...

I understand it is not the prettiest solution, but at least it is usable now.

@Cerna already explained this very well.

For completeness, I will add this QML example UI, making use of some more specialized UI controls. If you tell us which settings you want to control, we might be able to give you the commands/QML examples for modifying the settings. However, be aware that this type of custom UI will run only when a robot program is running, so anything that requires reloading of the program might be harder to achieve.

Here is the example containing also the SourcePanel and the PreviewPanel if required. PathPilot<control> components are the styled variants of the default QtQuick Controls 2 controls.

import QtQuick 2.0
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.2
import pathpilot.core 1.0
import pathpilot.controls 1.0
import pathpilot.panels.main 1.0
import pathpilot.handlers 1.0

RowLayout {
  id: root

  SourcePanel {
    visible: true
    folded: false
    Layout.fillHeight: true
  }

  ColumnLayout {
    Layout.fillWidth: false
    Layout.preferredWidth: 500

    RowLayout {
      Led {
        value: Config.user.custom.status == 1
      }

      PathPilotLabel {
        text: "Status 1"
      }

      Led {
        value: Config.user.custom.status == 2
      }

      PathPilotLabel {
        text: "Status 2"
      }
    }

    PathPilotButton {
      text: "Click Me"
      onClicked: Config.user.custom.request = true
    }

    VerticalFiller {
    }
  }

  PreviewPanel {
    visible: true
    Layout.fillWidth: true
    Layout.fillHeight: true
    Layout.preferredWidth: 500
  }
}

For an introduction to QtQuick you can take a look at https://qmlbook.github.io/.

Note: Parameter names are auto-converted from snake_case to camelCase in QML. So a parameter named “foo_bar” ends up as “fooBar” inside the QML UI.

Thank you. That’s all very helpful information. QML is new to me but I was able to create a functional control interface.

I have a couple follow up questions though:

Is there a way to run the program outside of PathPilot, or at least on a different screen or window? For context we are setting this up as part of a production line and we want it to be easily operated by anyone, as well as having the operator not be able to break anything.

Also, how do I stop PathPilot from switching constantly to the Status tab to warn me about the run duration?

@TJ_MacD I can’t answer your first question but @machinekoder might chime in.
Regarding status messages - if you change the log level to something higher (e.g. error or fatal) it won’t report run duration messages. If it’s still switching to status tab, make sure you’re on the latest release (3.15 as of 3/17). I know there were a few previous releases where lower-level messages would steal focus from the main screen regardless of log level setting.

Is there a way to run the program outside of PathPilot, or at least on a different screen or window? For context we are setting this up as part of a production line and we want it to be easily operated by anyone, as well as having the operator not be able to break anything.

Yes, this should be possible (with caveats).

By outside PathPilot, you mean outside the Robot UI window (application), outside the container namespace (the Tormach PathPilot Robot is running inside a Linux based container) or outside the PathPilot controller (the physical miniPC hardware)?

The loading of the program into the TPPR Interpreter, starting, pausing, stopping, unloading and checking happens via the ROS machinery. So, to load a program, you call a Service, to check the Interpreter status, you subscribe to a Topic.

In theory, your application can communicate with the Interpreter using these predefined method calls and then with your TRPL program using any RPC system you would like. (That way you would not be locked into Qt/QML but would be able to use any available engine, the same with programming language, only the TRPL program itself would need to use Python.)

We have some internal programs which we could rearrange a bit and turn them into customer-appropriate examples. (If you can wait a bit.)

The issue may be the availability of ROS message packages (these are so far only inside the container and mainly for Python) and in case you would want to command the robot from different machine, the networking will need a change, as the communication is now possible only via loopback.

The easiest way to achieve this is by creating a new ApplicationWindow inside the QML UI.
Note the “StayOnTop” hint which is required for this to work.

import QtQuick 2.0
import QtQuick.Controls 2.1
import QtQuick.Window 2.0
import QtQuick.Layouts 1.2
import pathpilot.core 1.0
import pathpilot.controls 1.0
import pathpilot.panels.main 1.0
import pathpilot.handlers 1.0

RowLayout {
  id: root
  
  PathPilotLabel {
    text: "Switch to other window"
  }
  
  ApplicationWindow {
    id: window
    visible: true
    flags: Qt.Window | Qt.WindowStaysOnTopHint
    width: 800
    height: 800
    title: "My Robot Program"
    visibility: Window.FullScreen
    
    onClosing: Handlers.program.stopProgram()
    
    Rectangle {
      anchors.fill: parent
      color: "darkgray"
    
      RowLayout {
        anchors.fill: parent
        anchors.margins: 10
        
        SourcePanel {
            visible: true
            folded: false
            Layout.fillHeight: true
          }

        ColumnLayout {
          Layout.fillWidth: false
          Layout.preferredWidth: 500

          RowLayout {
            Led {
              value: Config.user.custom.status == 1
            }

            PathPilotLabel {
              text: "Status 1"
            }

            Led {
              value: Config.user.custom.status == 2
            }

            PathPilotLabel {
              text: "Status 2"
            }
          }

          PathPilotButton {
            text: "Click Me"
            onClicked: Config.user.custom.request = true
          }
          
          PathPilotButton {
            text: "Stop"
            onClicked: Handlers.program.stopProgram()
          }

          VerticalFiller {
          }
        }
      }
    }
  }
}

An alternative option would be as @Cerna suggested, to create an external GUI.

Just coming back to this project after a while. All the information provided so far has been very helpful, so thank you.

Ideally the normal user would not have access to the Robot UI so they cannot edit the TRPL program or zero the positions or anything like that. They just need to be able to start and stop the robot and reset if the e-stop is triggered.

It seems like the best option for this would be to create a specialized UI program that communicates with the ROS, as you suggested. But I can’t find enough information on which topics and services to use and how to use them. I’m also not sure if I have the environment set up properly. More help on this would be appreciated.

This is in theory, of course, possible. However, the current state of TPPR does not allow for loading just the user program, without the Robot UI. At least not without significant hacking of the suite.

If I am understanding correctly, the holy grail of this would be to have an icon on the desktop which would launch a specific version of PathPilot (start the robot and load all the necessary machinery), load a specific TRPL program and open a single-purpose UI window using which would the user operate the robot, right?

Yes, I think that is basically what I need.