Client app Tools
12 minute read
Tools
This tutorial will show you some helpful tools to build a Motorcortex python client application. These tools are built on top of motorcortex-python and simplify common tasks such as setting up a client from scratch, adding new parameters like buttons, deploying to the Motorcortex application.
The first Client App Template tab will guide you through the installation and usage of a ready-to-use client application template that can be used as a starting point for your own client applications. The subsequent tabs will introduce additional tools and utilities that can enhance your client application development experience and will build upon the provided template. However, using the template is not a requirement to use the other principles shown in the other tabs.
Download of the Client Application Template
For the ease of use, a client application template is provided that sets up a basic Motorcortex-python client application. This template can be used as a starting point for your own client applications. It includes the necessary connection setup, parameter tree initialization, and provides a structure for running functions.
You can find the client application template on GitHub.
To use the template, clone the repository:
git clone hhttps://git.vectioneer.com/pub/mcx-client-app-template.git
Note
You can copy the files and folders from the git and work on your own project without using git if you prefer.
The file mcx-client-app.py is the main entry point for the client application. You can edit this file to add your own functionality and logic. The next tab will guide you through the mcx-client-app.py file and show how to extend it for your own use cases.
Structure of the Template
The structure of the template project is as follows:
mcx-client-app-template/
├── mcx-client-app.py # Main Python client app template for Motorcortex integration
├── mcx-client-app-legacy.py # Legacy version of the main Python client app template
│
├── makedeb.sh # Script to build a Debian package for MCX-RTOS deployment
├── collect_python_dependencies.sh # Script to collect non-pure Python dependencies for packaging
│
├── template.service.in # Template for the systemd service file
├── README.md # Project documentation and usage instructions
│
├── build/ # Build artifacts and generated Debian package files
│
├── examples/ # Example scripts demonstrating usage
│ ├── robot_app.py # Example: robot control application
│ └── start_button.py # Example: start button integration
├── src/ # Source code for the client app
│ └── mcx_client_app/
│ ├── __init__.py # Package initializer
│ ├── app.py # Main application logic
│ └── mcx_client_app.egg-info/ # Metadata for the Python package
The main application logic for the client app is contained in src/mcx_client_app and can thus be easily imported as a module in your own projects. Additional packages can be added to the src folder as needed for your own applications.
mcx-client-app.py
To create everything you want, the main file to edit is mcx-client-app.py. This file contains the main logic of the client application and is where you can add your own functionality and logic. It has been made easy to extend and modify according to your needs.
The bare minimum to get started is as follows, do not forget to replace the login, password and target_url with your own values:
from src.mcx_client_app import McxClientApp, MCXClientAppOptions # Import the main client app class and options
class MyClientApp(McxClientApp):
def __init__(self, options: MCXClientAppOptions):
super().__init__(options) # Initialize the base class with the provided options
def action(self):
print("Client app is running...") # Define the action to be performed when the client app runs
if __name__ == "__main__":
client_options = MCXClientAppOptions(
login="",
password="",
target_url=""
)
app = MyClientApp(client_options) # Create an instance of your client app with the specified options
app.run() # Run the client app with the specified
When you run this code, it creates a basic Motorcortex-python client application that connects to your specified Motorcortex server. By default, since no custom logic is added yet, the application will simply connect and repeatedly print “Client app is running…” to the console.
The run method manages the entire lifecycle of the connection: it connects to the Motorcortex server, engages the robot if needed, and then continuously calls your action method in a loop. When you stop the application, it will automatically disengage the robot and disconnect safely from the server.
To build your own functionality, you should implement the action method. This is where you can read parameters, send commands, or add any custom logic your client needs. You can also override other methods from the McxClientApp class to further customize your application’s behavior.
- Use the
startOpmethod to add logic that should run once when the client starts (for example, initializing parameters or setting up subscriptions). - Use the
onExitmethod to add cleanup logic that should run when the client stops (such as saving state or releasing resources).
Only the action method is required; startOp and onExit are optional and can be implemented as needed.
Note
The action method runs in the same thread as the rest of the application logic. Avoid blocking calls or long computations inside action. If you use loops, regularly check the boolean self.running to determine if the application should keep running.
Subscribing to Parameters
To subscribe to parameters and receive updates, you can use the usual self.sub.subscribe. However, it is important to note that subscriptions should be created in the startOp method, which is called once after the connection is established. This ensures that the subscriptions are set up correctly and ready to receive updates. Additionally, you can use the notify method on the subscription to define a callback function that will be executed whenever the subscribed parameter changes but this happens in a different thread. Thus, be cautious when accessing shared data between threads.
The class ThreadSafeValue can be used to safely share data between the subscription callback and the main thread where action runs. This class has the methods get() and set(value) to access and modify the value in a thread-safe manner.
Running action in a Separate Thread: McxClientAppThread
If your client application needs to perform long-running or blocking operations in the action method, you can use the McxClientAppThread class instead of McxClientApp. With McxClientAppThread, the action method runs in its own thread, while the main thread continues to manage the connection and other logic. This separation allows your application to remain responsive, even if action takes time to complete.
Note
When using McxClientAppThread, be aware that stopping the script will interrupt the action thread. This means any ongoing work in action may not finish cleanly. Make sure to handle cleanup and state saving appropriately in your implementation.
To safely share data between the main thread and the action thread, use the provided ThreadSafeValue class. This utility ensures that data access is synchronized, preventing race conditions and ensuring data integrity.
Example of using McxClientAppThread with ThreadSafeValue:
from src.mcx_client_app import McxClientAppThread, McxClientAppOptions, ThreadSafeValue
class ToolPoseMonitorApp(McxClientAppThread):
"""
Application that monitors and prints the tool pose.
"""
def __init__(self, options: McxClientAppOptions):
super().__init__(options)
self.toolPoseSubscription = None
self.toolPose: ThreadSafeValue[list[float]] = ThreadSafeValue([0, 0, 0, 0, 0, 0])
def startOp(self) -> None:
"""
Subscribe to tool pose updates.
"""
self.toolPoseSubscription = self.sub.subscribe(
'root/ManipulatorControl/manipulatorToolPoseActual',
"Group1",
frq_divider=1000
)
self.toolPoseSubscription.notify(lambda values: self.toolPose.set(values[0].value))
def action(self) -> None:
"""
Print current tool pose.
"""
print(f"Toolpose {self.toolPose.get()}")
self.wait(1)
if __name__ == "__main__":
client_options = McxClientAppOptions(
login="",
password="",
target_url="",
)
app = ToolPoseMonitorApp(client_options)
app.run()
In this example, the application subscribes to the tool pose parameter and prints its value every second. The ThreadSafeValue class is used to safely share the tool pose data between threads. The startOp method sets up the subscription after the connection is established, and the action method handles the periodic printing in its own thread.
Tip
Use McxClientAppThread when your action method needs to perform blocking or time-consuming operations, but always ensure proper thread-safe data handling and graceful shutdown.
Start button
You can easily add a start button to your Motorcortex-python client application using the start_stop_param parameter in MCXClientAppOptions. This button allows you to start and stop your client application directly from the Motorcortex GUI. When the button is pressed, the action method of your client application will be executed. Pressing the button again will stop the execution of the action method.
- Firstly, add the parameter to the tree in your Motorcortex application. You can do this by adding a boolean parameter to
.conf/user/parameters.jsonin the motorcortex portal. For example, you can add the following parameter add the bottom of the file:
{
"Name": "GUI",
"Children": [
{
"Name": "PythonScript01",
"Children": [
{
"Name": "StartStop",
"Type": "bool, input",
"Value": 0
}
]
}
]
}
- Next, in your
mcx-client-app.py, set thestart_stop_paramoption inMCXClientAppOptionsto the path of the start button parameter you just created. For example:
client_options = MCXClientAppOptions(
login="",
password="",
target_url="",
start_stop_param="root/UserParameters/GUI/PythonScript01/StartStop" # Path to the start button parameter
)
- Add a switch in your GUI to interact with the start button parameter or use the DESK-Tool to toggle the button by manually changing its value.
Other Custom buttons
Other buttons with other logic can be added in a similar way by creating boolean parameters in the Motorcortex application and reading their values in the action method of your client application or by creating a subscription in startOp method. You can implement custom logic based on the state of these buttons to control various aspects of your client application.
For example, you can create a button to trigger a reset to change values:
from src.mcx_client_app import McxClientApp, McxClientAppOptions, ThreadSafeValue
class CustomButtonApp(McxClientApp):
"""
Application that monitors and prints the tool pose.
"""
def __init__(self, options: McxClientAppOptions):
super().__init__(options)
self.buttonSubscription = None
self.counter: int = 0
self.__reset: ThreadSafeValue[bool] = ThreadSafeValue(False)
def startOp(self) -> None:
"""
Subscribe to button updates.
"""
self.buttonSubscription = self.sub.subscribe(
'root/UserParameters/GUI/PythonScript01/resetButton',
"Group1",
frq_divider=10
)
self.buttonSubscription.notify(self.__button_callback)
self.req.setParameter('root/UserParameters/GUI/PythonScript01/Counter', 0).get()
def __button_callback(self, msg) -> None:
"""Callback for button press - only trigger on rising edge (0 -> 1). (Happens in the subscription thread)"""
value = msg[0].value[0]
if value != 0: # Button pressed (rising edge)
self.__reset.set(True)
def __reset_counter(self) -> None:
self.counter = 0
self.__reset.set(False)
self.req.setParameter('root/UserParameters/GUI/PythonScript01/resetButton', 0).get()
def action(self) -> None:
"""
Increment counter and check for reset button press.
"""
if self.__reset.get():
self.__reset_counter()
print("Counter reset!")
else:
self.counter += 1
print(f"Counter: {self.counter}")
self.req.setParameter('root/UserParameters/GUI/PythonScript01/Counter', self.counter).get()
self.wait(1) # Wait 1 second between increments
if __name__ == "__main__":
client_options = McxClientAppOptions(
login="",
password="",
target_url="",
)
app = CustomButtonApp(client_options)
app.run()
For this example, the parameter root/UserParameters/GUI/PythonScript01/resetButton should be created as a boolean input parameter in the Motorcortex application abd . When the button is pressed, the counter will reset to zero, and the button will be set back to 0 after the reset action is performed.
Packaging your Client Application for Deployment
Instead of running the client application on a seperate pc that connects to the Motorcortex server over the network, you can also deploy the client application directly to the Motorcortex application. This can be useful for standalone setups or when you want to ensure that the client application is always running alongside the Motorcortex server.
A convenient file, makeDeb.sh, has been created to help you package your client application into a .deb file, which can then be installed on the Motorcortex application. This script automates the process of creating the necessary directory structure and files for a Debian package.
There are a few steps to follow to successfully create and deploy your client application:
-
Ensure that your client application is working correctly on your local machine. Test it thoroughly to make sure it behaves as expected.
-
Modify the
makeDeb.shscript to include the correct paths and names for your client application. This script automates the process of creating the necessary directory structure and files for a Debian package, making it easy to package your client application into a.debfile for installation on the Motorcortex system.# Set variables PACKAGE_NAME="mcx-client-app" VERSION="1.0" ARCHITECTURE="all" MAINTAINER="Vectioneer B.V. <info@vectioneer.com>" DESCRIPTION="A client application for getting and setting data in a Motorcortex Control Application" PYTHON_SCRIPT="Test.py" PYTHON_MODULES="src" # directories, space separated list SERVICE="${PACKAGE_NAME}" BUILDFOLDER="build"Make sure to replace
PYTHON_SCRIPTwith the name of your main client application script andPYTHON_MODULESwith any additional directories that contain modules your application depends on. As the client templateMcxClientAppis in thesrcfolder, keep this folder in the list. -
Run the
makeDeb.shscript to create the.debpackage. This will generate a file namedmcx-client-app_1.0.deb(or similar, depending on your name and version). -
Upload the generated
.debfile to your Motorcortex binaries in the portal and deploy! -
After deployment, your client application should be installed and ready to run on the Motorcortex system. As it runs as a service, you can manage it and look at the logs in Cockpit under Services.
Additional Python packages
One of the powers of using Python for your client application is the vast ecosystem of available packages. A few packages are preinstalled on the Motorcortex application, but if your client application depends on additional packages, you must include them in your deployment package.
Click to expand: Default Python packages installed on the Motorcortex application
| Package | Version |
|---|---|
| btrfsutil | 5.16.2 |
| cairocffi | 1.1.0 |
| CairoSVG | 2.4.2 |
| cffi | 1.15.0 |
| cssselect2 | 0.3.0 |
| cycler | 0.11.0 |
| defusedxml | 0.7.1 |
| html5lib | 1.1 |
| Jinja2 | 3.1.4 |
| lxml | 4.8.0 |
| MarkupSafe | 2.1.1 |
| matplotlib | 3.5.1 |
| motorcortex-python | 0.22.9 |
| motorcortex-python-tools | 1.4.3 |
| numpy | 1.22.3 |
| pandas | 1.4.2 |
| Pillow | 9.4.0 |
| pip | 22.0.3 |
| ply | 3.11 |
| protobuf | 3.20.3 |
| pycparser | 2.21 |
| pynng | 0.7.1 |
| pyparsing | 3.0.7 |
| Pyphen | 0.9.5 |
| python-dateutil | 2.8.2 |
| pytz | 2022.1 |
| setuptools | 59.5.0 |
| six | 1.16.0 |
| smbus | 1.1 |
| sniffio | 1.2.0 |
| tinycss2 | 1.0.2 |
| UNKNOWN | 0.0.0 |
| WeasyPrint | 51 |
| webencodings | 0.5.1 |
A convenient way to include additional packages to the .deb package so they can be deployed with your client application is given below:
-
Edit the script
collect_python_dependencies.sh. Put inDepsLatest=the list of additional python packages you want to include. -
Transfer the script to a location accessible by the Motorcortex application, for example using
scp:scp collect_python_dependencies.sh user@remote # On your local machine -
ssh into your Motorcortex application or use a local development setup with the same OS as the Motorcortex application. Make sure internet access is available. For instructions on configuring the network, see Network Configuration.
-
Run the
collect_python_dependencies.shscript on the Motorcortex application. This will create a.tar.gzfile containing the specified python packages and their dependencies with an unique name in the form ofdependencies.XXXXXXXXXX.tar.gz. Take note of this name for the next step../collect_python_dependencies.sh -
Copy the generated
.tar.gzfile back to your development machine.scp user@remote:dependencies.XXXXXXXXXX.tar.gz . # On your local machine -
Modify the
makeDeb.shscript to include the generated.tar.gzfile in the package. You can do this by extracting the contents of the.tar.gzfile into the folderVENV_REQ_DIR=""that is chosen in themakeDeb.shscript. This way, when the.debpackage is installed on the Motorcortex application, the additional python packages will be available for your client application to use.
The next steps are the same as before: run the makeDeb.sh script to create the .deb package, upload it to your Motorcortex binaries in the portal, and deploy it to your Motorcortex application.
Note
When using additional python packages, deploying might take longer due to the increased size of the package. Make sure to test your client application thoroughly after deployment to ensure that all dependencies are correctly installed and functioning as expected.