Client app Tools
109 minute read
Client App 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.
Vscode extension
With the Vscode Extension, you can easily scaffold the Client app Template with the scaffold new Motorcortex Project and choose Python MCX Client App in the side bar or use the command motorcortex.createMcxClientApp. This will setup the folder, python environment, and will fill in some configs for you.
You can find the client application template on GitLab.
To use the template, clone the repository:
git clone https://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.
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 create a service config file, see the Configurations tab, you can use the provided template config or create your own. Put at least the login, password, target_url, and cert fields in it.
Example service configuration
{
"Version": "1.0.0",
"Services": [
{
"Name": "mcx-client-app",
"Enabled": true,
"Parameters": {
"Version": "1.0",
"Children": []
},
"Config": {
"login": "admin",
"password": "vectioneer",
"target_url": "wss://192.168.2.100",
"cert": "mcx.cert.crt"
},
"Watchdog": {
"Enabled": true,
"Disabled": true,
"high": 1000000,
"tooHigh": 5000000
}
}
]
}
mcx-client-app.py:
import logging
logging.basicConfig(level=logging.INFO)
from src.mcx_client_app import McxClientApp, McxClientAppConfiguration, ThreadSafeValue
class ExampleMcxClientApp(McxClientApp):
"""
Application that you can change to your needs.
"""
def __init__(self, options: McxClientAppConfiguration):
super().__init__(options)
# Initiate your variables here
def startOp(self) -> None:
"""
Subscribe to button updates.
"""
# Start your subscriptions here and set values after connection is established
pass
def iterate(self) -> None:
"""
Increment counter and check for reset button press.
"""
print("Client app is running...")
self.wait(1) # Wait 1 second between increments
def onExit(self) -> None:
"""
Clean up on exit.
"""
# Unsubscribe from subscriptions here
pass
if __name__ == "__main__":
client_options = McxClientAppConfiguration()
client_options.set_config_paths(
deployed_config="/etc/motorcortex/config/services/mcx_client_app.json", #This is only needed when deployed on a Motorcortex controller. If only locally running, you can set it to None.
non_deployed_config="config.json"
)
app = ExampleMcxClientApp(client_options)
app.run()
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 iterate 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 iterate 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 has connected to (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 iterate method is required; startOp and onExit are optional and can be implemented as needed.
Note
The iterate method runs in the same thread as the rest of the application logic. Avoid blocking calls or long computations inside iterate. If you use loops, regularly check the boolean self.running.get() 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 iterate runs. This class has the methods get() and set(value) to access and modify the value in a thread-safe manner.
Running iterate in a Separate Thread: McxClientAppThread
If your client application needs to perform long-running or blocking operations in the iterate method, you can use the McxClientAppThread class instead of McxClientApp. With McxClientAppThread, the iterate 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 iterate takes time to complete.
Warning
When using McxClientAppThread, be aware that stopping the script will interrupt the iterate thread. This means any ongoing work in iterate 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 iterate 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, McxClientAppConfiguration, ThreadSafeValue
class ToolPoseMonitorApp(McxClientAppThread):
"""
Application that monitors and prints the tool pose.
"""
def __init__(self, options: McxClientAppConfiguration):
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 iterate(self) -> None:
"""
Print current tool pose.
"""
print(f"Toolpose {self.toolPose.get()}")
self.wait(1)
if __name__ == "__main__":
client_options = McxClientAppConfiguration()
client_options.set_config_paths(
deployed_config="/etc/motorcortex/config/services/mcx_client_app.json", #This is only needed when deployed on a Motorcortex controller. If only locally running, you can set it to None.
non_deployed_config="config.json"
)
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 iterate method handles the periodic printing in its own thread.
Tip
Use McxClientAppThread when your iterate method needs to perform blocking or time-consuming operations, but always ensure proper thread-safe data handling and graceful shutdown.
Service Configuration
Service configuration files define how your client applications are configured and deployed on a Motorcortex controller. These JSON files specify connection parameters, watchdog settings, and custom configuration options for each service.
To register service configuration files, add them to the "Path" section in your project’s main config.json:
"Path": {
"ServicesConfig": [
"services/service_config.json"
],
}
Motorcortex will load and apply all configuration files listed in ServicesConfig. Each file uses the following structure to define services:
{
"Version": "1.0.0",
"Services": [
{
"Name": "mcx-client-app",
"Enabled": true,
"Parameters": {
"Version": "1.0",
"Children": []
},
"Config": {
"login": "admin",
"password": "vectioneer",
"target_url": "wss://192.168.2.100",
"cert": "mcx.cert.crt"
},
"Watchdog": {
"Enabled": true,
"Disabled": true,
"high": 1000000,
"tooHigh": 5000000
}
}
]
}
Field descriptions:
- Version: Configuration file format version (e.g.,
"1.0.0"). - Services: Array containing one or more service configurations.
- Name: Unique service identifier. Must match the
nameparameter in yourMcxClientAppConfiguration. - Enabled: Boolean flag that determines whether the service should be started (
true) or remain inactive (false). - Parameters: Service parameters added to the datatree. Uses the same format as
userparameters.json. - Config: Python-side configuration that maps to attributes in your
McxClientAppConfigurationor custom configuration class. These values are loaded when callingload_config(). - Watchdog: Optional watchdog configuration for monitoring service health and performance thresholds.
- Name: Unique service identifier. Must match the
All fields defined in the Config section are automatically loaded into your configuration class when you call load_config().
Backward compatibility
In Motorcortex-python versions prior to v3.14.0, Motorcortex does not load services into the tree. Client applications still function without the Watchdog and error handler features, but service parameters must be manually added to userparameters.json. All other functionality remains unchanged on the Python side.
McxClientAppConfiguration
In the template examples, the class McxClientAppConfiguration is used to configure and initialize the McxClientApp. This class holds all the configuration options required to set up the client application. You can create an instance of this class and pass it to the McxClientApp constructor. The function set_config_paths is used to specify the paths to the configuration files for both deployed and non-deployed environments.
client_options = McxClientAppConfiguration(name="ExampleMcxClientApp")
client_options.set_config_paths(
deployed_config="/etc/motorcortex/config/services/services_config.json", #This is only needed when deployed on a Motorcortex controller. If only locally running, you can set it to None.
non_deployed_config="services_config.json"
)
client_options.load_config()
app = ExampleMcxClientApp(client_options)
app.run()
More information
When deploying, it will automatically use the deployed configuration file, otherwise it will use the non-deployed configuration file. Behind the scenes, load_config looks for and environment variable called DEPLOYED, when this is True (set automatically when using makeDeb.py, see deploying for more information), it will use the deployed configuration file, otherwise it will use the non-deployed configuration file.
It is possible to not set the config paths and directly create an instance of McxClientAppConfiguration with the desired parameters. However, using configuration files is recommended for better manageability and flexibility, especially when deploying the application across different environments:
client_options = McxClientAppConfiguration(
login="admin",
password="vectioneer",
target_url="wss://192.168.2.100"
)
app = ExampleMcxClientApp(client_options)
app.run()
Note
target_url and cert paths are not used when deployed, as these will be set automatically by the deployment environment. If you want to change these values, you can use target_url_deployed and cert_deployed, respectively, instead. This means you can use the same json file for both local development and delopyment.
Information on deployed_config (important)
The default path for deployed_config is setup so all the configs of services are stored in the service folder which you can deploy from the portal. This is recommended as it keeps all service configurations in one place and have a convenient way of deploying them.
(If you do not have the services folder, you can create one.)
Default McxClientAppConfiguration Attributes
- name (
str): Name of the client application. Used as the service name when loading deployed configuration. - login (
str): Username for authenticating with the Motorcortex server. - password (
str): Password for authenticating with the Motorcortex server. - target_url (
str): Local Development WebSocket URL of the Motorcortex server (e.g.,wss://localhost). This is the endpoint used to establish the connection. - target_url_deployed (
str): Deployed WebSocket URL of the Motorcortex server (default:wss://localhost). This is the endpoint used when the application is deployed on a system. - cert (
str): Local Development path to the SSL certificate file for secure connection (e.g.,mcx.cert.crt). Required for encrypted communication with the server. This is only used with local development (not deployed). - cert_deployed (
str): Deployed path to the SSL certificate file for secure connection (default:/etc/ssl/certs/mcx.cert.pem). Required for encrypted communication with the server when deployed on a system with the certificate installed. - statecmd_param (
str): Parameter path for sending state commands to the server (default:root/Logic/stateCommand). Used to control the robot or system state. - state_param (
str): Parameter path for reading the current state from the server (default:root/Logic/state). Used to monitor the robot or system state. - run_during_states (
list[State]|None): List of allowed states during which theiterate()method can run (default:None). If the system is not in one of these states, theiterate()method will not execute. If empty orNone, theiterate()method can run in any state. - autoStart (
bool): Whether the application should start automatically upon connection (default:True). - start_button_path (
str|None): Parameter path for a start button to control auto-start behavior (default: None). If set, the application will use that path for start/stop control (default:root/serices/{name}/enableService) - enable_watchdog (
bool): Whether to enable the watchdog functionality (default:True). - enable_error_handler (
bool): Whether to enable the error handler functionality (default:True). - error_reset_param (
str): Parameter path that indicates when to reset errors (default:root/Services/:fromState/resetErrors). - deployed_config (
str): Path to the deployed configuration file (default:/etc/motorcortex/config/services/services_config.json). - non_deployed_config (
str|None): Path to the non-deployed (local) configuration file. May beNoneby default.
Customizing McxClientAppConfiguration
You can create your own subclass of McxClientAppConfiguration to add custom configuration options specific to your client application. This allows you to extend the configuration capabilities while still leveraging the existing functionality of the base class. For example, when deployed, you can also change and deploy custom parameters via the portal.
Tip
Use this for configuration parameters that are not meant to be changed at runtime. For example, login credentials, parameter paths, like root/Logic/stateCommand, robotic arm settings, or other static configuration values.
To create a custom options class, simply inherit from McxClientAppConfiguration and add your own attributes and methods as needed.
When inheriting from McxClientAppConfiguration, initialize any custom attributes first, then call super().__init__(**kwargs). This ensures the base-class initialization runs with your finalized attribute values. For example:
class CustomOptions(McxClientAppConfiguration):
def __init__(self, custom_param: str = "default", **kwargs):
self.custom_param = custom_param
super().__init__(**kwargs)
Example Usage
This example will be a short snippet showing how to create a custom configuration class and use it with McxClientApp. We will also deploy it and then change the configuration via the portal.
In this example, we will add a custom parameter called print_integer that will be printed in the iterate() method of the client application.
-
Firstly, create a custom configuration class by inheriting from
McxClientAppConfiguration. We will add a custom parameter calledprint_integer.from src.mcx_client_app import McxClientApp, McxClientAppConfiguration import logging logging.basicConfig(level=logging.INFO) class CustomMcxClientAppConfiguration(McxClientAppConfiguration): """ Custom configuration for the ExampleMcxClientApp. Attributes: print_integer (int): Number that will be printed in the iterate method. """ def __init__(self, print_integer: int = 10, **kwargs): self.print_integer = print_integer super().__init__(**kwargs) -
Next, change the iterate method of the client application to print the custom parameter.
class ExampleMcxClientApp(McxClientApp): """ Application that you can change to your needs. """ def __init__(self, options: McxClientAppConfiguration): super().__init__(options) def iterate(self) -> None: """ Increment counter and check for reset button press. """ self.wait(1) # Wait 1 second between increments logging.info(f"Number: {self.options.print_integer}") -
Update the
services_config.jsonwith the following content (Do not forget to set your login credentials and target_url):{ "Version": "1.0.0", "Services": [ { "Name": "print-integer-client-app", "Enabled": true, "Config": { "login": "", "password": "", "target_url": "", "print_integer": 42 } } ] }Here, we set the
print_integerparameter to42. -
Finally, create an instance of the custom configuration class using the
set_config_pathshelper method and pass it to theExampleMcxClientAppconstructor.if __name__ == "__main__": client_options = CustomMcxClientAppConfiguration() client_options.set_config_paths( deployed_config="/etc/motorcortex/config/services/services_config.json", #This is only needed when deployed on a Motorcortex controller. If only locally running, you can set it to None. non_deployed_config="services_config.json" ) app = ExampleMcxClientApp(client_options) app.run()When you run this application, it will print the value of
print_integer(which is42in this case) every second.INFO:root:Waiting for start signal... INFO:root:Running user iterate... INFO:root:Number: 67 INFO:root:Number: 67 INFO:root:Number: 67 INFO:root:Number: 67 INFO:root:Number: 67 INFO:root:Number: 67 INFO:root:Number: 67 ^CINFO:root:Keyboard interrupt received. Shutting down... -
Go to the project in the Motorcortex portal where you want to test out the script and navigate to the “.conf” folder. Create a new folder called ‘services’ if it does not exist yet. Then, change, or create, the
services_config.jsonto include in the services list:{ "Name": "mcx-client-app", "Enabled": true, "Parameters": { "Version": "1.0", "Children": [] }, "Config": { "login": "admin", "password": "vectioneer", "target_url": "wss://192.168.2.100" }, "Watchdog": { "Enabled": true, "Disabled": true, "high": 1000000, "tooHigh": 5000000 } }Then deploy the
.conffolder. The deployed configuration file will be used instead of the local file when the application is deployed.See the top to
-
Create a debian package for deployment, see the
deployingguide for more information on deployment, and deploy it via the Motorcortex portal in any project. -
Restart Motorcortex and see the output in the services section of Cockpit.
Start button
You can setup a client app to start and stop its iterate method using a button in the Motorcortex GUI. By default, the McxClientApp class automatically starts but this can be disabled. Then you can hook a button up to enableService parameter to start and stop the iterate method.
- In your
services_config.jsonfile, set theautoStartparameter tofalse:
{
"PACKAGE_NAME": {
"Config": {
"autoStart": false
}
}
}
- Create a switch in the Motorcortex GUI that is linked to the
root/services/PACKAGE_NAME/enableServiceboolean parameter. When this switch is toggled on, theiteratemethod will start executing. When toggled off, it will stop.
Backward compatibility
In older versions of Motorcortex-python, where enableService is not present, the path to the start button can be set in services_config.json with the configuration: start_button_path
Other Custom buttons
Other buttons with other logic can be added using the built-in commandWord parameter. The commandWord parameter is automatically created at root/Services/{ServiceName}/commandWord and allows external systems or GUI buttons to send commands to your service.
For this example, the basic Motorcortex Anthropomorphic Robot application is used. You can download it from the Motorcortex Store. Make sure to have the Motorcortex Anthropomorphic Robot application running and that you can connect to it using the DESK-Tool. But other Motorcortex applications will work as well.
This example shows how to use the commandWord parameter to reset a counter when a specific command is received. Send a command by setting commandWord to a specific value (e.g., 1 for reset, 2 for double).
Tip
Use commandWord when you need to trigger specific actions from external systems (GUI buttons, other services, or manual commands via DESK). For simple on/off controls, use boolean parameters with subscriptions instead.
When to use commandWord:
- ✅ Multiple distinct commands (reset, start, stop, calibrate, etc.)
- ✅ Commands triggered from external systems or GUI
- ✅ Need to acknowledge commands after processing
- ❌ Simple on/off toggle (use boolean parameter instead)
- ❌ Continuous value updates (use regular parameters with subscriptions)
- In
services_config.json, define your service parameters:
{
"Name": "CustomButtonExample",
"Enabled": true,
"Parameters": {
"Version": "1.0",
"Children": [
{
"Name": "Counter",
"Type": "int, parameter_volatile",
"Value": 0
}
]
},
"Config": {
"login": "admin",
"password": "vectioneer",
"target_url": "wss://192.168.2.100",
"autoStart": true
},
"Watchdog": {
"Enabled": true,
"Disabled": true,
"high": 2000000,
"tooHigh": 5000000
}
}
- Create your application code:
Step 1: Import required modules and set up logging
from src.mcx_client_app import McxClientApp, McxClientAppConfiguration
from src.mcx_client_app.ChangeDetector import ChangeDetector
import motorcortex
import logging
logging.basicConfig(level=logging.INFO)
Step 2: Initialize the application class
class CustomButtonApp(McxClientApp):
"""
Application that counts and processes commands via commandWord parameter.
This example demonstrates using the ChangeDetector class to monitor the commandWord parameter
and process commands only when the value changes.
"""
def __init__(self, options: McxClientAppConfiguration):
super().__init__(options)
self.command_detector: ChangeDetector = ChangeDetector()
self.counter: int = 0
self.command_subscription = None
The ChangeDetector helps track when commandWord changes, so we only process commands when a new value is set (not on every subscription update).
Step 3: Subscribe to commandWord in startOp
def startOp(self) -> None:
"""
Subscribe to commandWord updates and initialize counter.
"""
# Subscribe to commandWord parameter
self.command_subscription = self.sub.subscribe(
[f'{self.options.get_parameter_path}/commandWord'],
"commandWord_group",
frq_divider=10
)
result = self.command_subscription.get()
if result and result.status == motorcortex.OK:
self.command_subscription.notify(self._on_command_update)
# Initialize counter parameter
self.req.setParameter(f'{self.options.get_service_parameter_path}/Counter', 0).get()
- Subscribe to
commandWordatroot/Services/CustomButtonExample/commandWord - The callback
_on_command_updatewill be called whenever the parameter tree updates - Initialize the
Counterparameter to 0
Step 4: Handle commandWord updates in callback (keep it fast!)
def _on_command_update(self, msg) -> None:
"""
Callback for commandWord updates (runs in subscription thread).
Just updates the detector value.
"""
value = msg[0].value[0]
self.command_detector.set_value(value)
The callback runs in the subscription thread and is called on every tree update. Keep it minimal - just extract the value and store it in the ChangeDetector.
Step 5: Process commands in iterate
def iterate(self) -> None:
"""
Increment counter and check for commandWord changes.
"""
# Check if commandWord changed and process command
if self.command_detector.has_changed(trigger_on_zero=False):
value = self.command_detector.get_value()
logging.info(f"Command received: {value}")
if value == 1:
logging.info("Resetting counter!")
self.counter = -1
elif value == 2:
logging.info("Doubling counter!")
self.counter *= 2
# Acknowledge command by resetting commandWord to 0
self.req.setParameter(f"{self.options.get_parameter_path}/commandWord", 0).get()
self.counter += 1
print(f"Counter: {self.counter}")
self.req.setParameter(f'{self.options.get_service_parameter_path}/Counter', self.counter).get()
self.wait(1.0)
has_changed(trigger_on_zero=False): Only returnsTruewhencommandWordchanges to a non-zero value- Process different commands based on the value (1 = reset, 2 = double)
- Important: Acknowledge the command by resetting
commandWordback to 0 - Continue with normal application logic (increment counter)
Step 6: Configure and run the application
if __name__ == "__main__":
client_options = McxClientAppConfiguration(
name="CustomButtonExample"
)
client_options.set_config_paths(
deployed_config="/etc/motorcortex/config/services/services_config.json",
non_deployed_config="services_config.json"
)
client_options.load_config()
app = CustomButtonApp(client_options)
app.run()
How it works:
- External system sets
commandWordto1(via GUI button or DESK) - Subscription callback updates
ChangeDetectorwith the new value iterate()detects the change and processes command 1 (reset counter)- Application acknowledges by setting
commandWordback to0 - Ready for the next command
Note
The commandWord parameter is automatically created at root/Services/{ServiceName}/commandWord. Use self.options.get_parameter_path to access service-level parameters like commandWord, and self.options.get_service_parameter_path for custom service parameters defined in the Parameters section.
To ensure Motorcortex handles unexpected crashes or freezes of your client application gracefully, you can implement a watchdog mechanism. This involves periodically sending heartbeat signals from your client application to Motorcortex, indicating that it is running correctly.
In the standard McxClientApp, a watchdog is already implemented by default. It sends heartbeat signals at regular intervals to Motorcortex. If Motorcortex does not receive a heartbeat within a specified timeout period, it assumes the client application has crashed or frozen and takes appropriate action, such as throwing a warning or even throwing an emergency stop.
Watchdog Configurations
Backward compatibility
In older versions of Motorcortex-python (pre v3.14.0), where watchdog is not yet supported, it is important to set enable_watchdog for the client app to false
In the services_config.json file, you can configure the watchdog settings for your client application:
{
"PACKAGE_NAME": {
"Config": {
"enable_watchdog": true
},
"Watchdog": {
"Enabled": true,
"Disabled": true,
"high": 1000000,
"tooHigh": 5000000
}
}
}
The enable_watchdog parameter in the Config section enables or disables the watchdog functionality in the client app. While the Watchdog section contains specific settings for the watchdog behavior in Motorcortex:
Enabled: If set totrue, the watchdog is active and monitors the heartbeat signals.Disabled: If set totrue, the watchdog is inactive and does not monitor heartbeat signals.high: The threshold (in microseconds) for a delayed heartbeat. If the heartbeat is delayed beyond this value, Motorcortex will log a warning.tooHigh: The threshold (in microseconds) for a missed heartbeat. If the heartbeat is not received within this value, Motorcortex will assume the client application has crashed and take emergency action.
The McxErrorHandler allows your client application to trigger system-level errors at different severity levels in Motorcortex. This is essential for integrating your application into the overall system safety and error management.
Backward compatibility
In older versions of Motorcortex-python (pre v3.14.0), a client app is unable to registers errors and warning int Motorcortex.
Overview
Every McxClientApp instance has a built-in error handler accessible via self.errorHandler. The error handler:
- Triggers errors at 5 different severity levels (INFO, WARNING, FORCED_DISENGAGE, SHUTDOWN, EMERGENCY_STOP)
- Uses subsystem IDs to identify which service or component triggered the error
- Uses error codes to describe what the error is (e.g., battery low, sensor failure, timeout)
- Supports acknowledgment callbacks to reset your application state when errors are cleared
Error Severity Levels
The error handler supports 5 severity levels (from MotorcortexErrorLevel enum):
| Level | Value | Description | System Response |
|---|---|---|---|
ERROR_LEVEL_UNDEFINED |
0 | Undefined/cleared error | Clears active errors |
INFO |
1 | Information message | No system action, just logging |
WARNING |
2 | Warning condition | System continues running |
FORCED_DISENGAGE |
3 | Graceful software stop | Disengages system gracefully |
SHUTDOWN |
4 | Abrupt software stop | Immediate software shutdown |
EMERGENCY_STOP |
5 | Hardware emergency stop | Software AND hardware stop |
Severity Progression: INFO < WARNING < FORCED_DISENGAGE < SHUTDOWN < EMERGENCY_STOP
Subsystem IDs - Identifying Error Sources
Subsystem IDs identify which service or component triggered an error. This is critical for diagnosing issues in complex systems with multiple services.
Best Practices:
One Subsystem ID per Service:
- If your client app is a single logical service (e.g., “BatteryMonitor”), use one subsystem ID for the entire service
- Set the subsystem ID in
startOp()and use it for all errors in that service
def startOp(self) -> None:
# Set subsystem ID for this service
self.errorHandler.set_subsystem_id(1) # BatteryMonitor = subsystem 1
Multiple Subsystem IDs within a Service:
- If your service has distinct subsystems (e.g., “FleetManager” managing multiple robots), use different IDs for each
- Pass
subsystem_idparameter when triggering errors to distinguish between subsystems
class FleetManager(McxClientApp):
def __init__(self, options):
super().__init__(options)
self.ROBOT_1_SUBSYSTEM = 10
self.ROBOT_2_SUBSYSTEM = 11
self.ROBOT_3_SUBSYSTEM = 12
def check_robot_1(self):
if battery_low:
# Specify which robot has the error
self.errorHandler.trigger_warning(
error_code=1001,
subsystem_id=self.ROBOT_1_SUBSYSTEM
)
Subsystem ID Guidelines:
- Use unique IDs across your entire system to avoid conflicts
- Document your subsystem ID allocation (e.g., 1-10 for service A, 11-20 for service B)
- Reserve ID 0 for system-level errors (undefined subsystem)
- Use consistent IDs - don’t change them between runs
Error Codes - Describing What Happened
Error codes describe the specific condition that triggered the error. Think of them as unique identifiers for different failure modes.
Example
class BatteryMonitor(McxClientApp):
# Error code definitions
ERROR_BATTERY_LOW = 1001
ERROR_BATTERY_CRITICAL = 1002
ERROR_CHARGING_FAULT = 1003
ERROR_TEMPERATURE_HIGH = 1004
ERROR_COMMUNICATION_LOST = 1005
def check_battery(self):
if battery_voltage < self.CRITICAL_THRESHOLD:
self.errorHandler.trigger_emergency_stop(
error_code=self.ERROR_BATTERY_CRITICAL
)
elif battery_voltage < self.LOW_THRESHOLD:
self.errorHandler.trigger_warning(
error_code=self.ERROR_BATTERY_LOW
)
Example
1. Configure Error Handler in startOp():
This step sets up the error handler for your service. By assigning a subsystem ID, you ensure that all errors triggered by this instance are correctly attributed in the system. Setting an acknowledgment callback allows your app to respond when an operator clears an error, making it possible to reset state or resume operations safely.
def startOp(self) -> None:
"""Initialize error handler with subsystem ID."""
# Set subsystem ID for this service
self.errorHandler.set_subsystem_id(1)
# Optional: Set callback for error acknowledgment
self.errorHandler.set_acknowledge_callback(self.on_error_acknowledged)
logging.info("Error handler configured for subsystem 1")
2. Trigger Errors in Your Code:
In your main loop or monitoring function, you check system conditions (like battery voltage) and use the error handler to report issues. Each call to the error handler includes an error code, which helps identify the specific problem. Severity is chosen based on the condition—critical errors trigger an emergency stop, while less severe issues trigger warnings.
def iterate(self) -> None:
"""Monitor conditions and trigger appropriate errors."""
battery_voltage = self.get_battery_voltage()
if battery_voltage < 10.5:
# Critical - stop everything immediately
self.errorHandler.trigger_emergency_stop(error_code=1002)
elif battery_voltage < 11.0:
# Low battery warning
self.errorHandler.trigger_warning(error_code=1001)
self.wait(1.0)
3. Handle Error Acknowledgment (Optional):
When an error is acknowledged by the operator (for example, after resolving the issue), the callback is invoked. Here, you can reset counters, clear flags, or perform any recovery logic needed to bring your application back to a safe state.
def on_error_acknowledged(self) -> None:
"""Called when operator acknowledges the error."""
logging.info("Error acknowledged - resetting application state")
# Reset your error conditions
self.error_count = 0
self.retry_attempts = 0
# Clear any error flags
self.req.setParameter(
f"{self.options.get_service_parameter_path}/ErrorActive",
0
).get()
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 Motorcortex.
Vscode extension
The Vscode Extension will guide you through the deployment process and it, thus, recommended to be used.
A docker container is used to create and package the client application into a .deb file that can be deployed to the Motorcortex application. This container provides a consistent environment for building the package, ensuring that all necessary dependencies and configurations are included. Optionally, you can also use a python script to create the .deb on your local machine without using docker but additional python dependencies might be broken on the motorcortex application when deployed.
After creating a .deb file, it can be deployed to the controller using the Portal or manually via SSH after which you should install the package using dpkg -i <package>.deb.
For convenience, all deployment-related configurations can be specified in a single package_config.json file.
The first step in deploying your client application is to create a package_config.json file that contains the necessary configuration for building the Debian package. This file specifies various options such as the package name, the main Python script to run, any additional Python modules required, and other settings related to the deployment.
All the file paths specified in the package_config.json file should be relative to the root of your client application project from where you will run the deployment command.
An example package_config.json file is shown below:
{
"PACKAGE_NAME": "mcx-client-app",
"PYTHON_SCRIPT": "mcx-client-app.py",
"PYTHON_MODULES": "src",
"VERSION": "1.0",
"MAINTAINER": "Vectioneer B.V. <info@vectioneer.com>"
}
Required options
PACKAGE_NAME: The name of the Debian package (e.g., “mcx-client-app”). Stick to lowercase letters, numbers, and hyphens!PYTHON_SCRIPT: The main Python script file to execute (e.g., “mcx-client-app.py”).PYTHON_MODULES: Space-separated list of Python module directories to include in the package (e.g., “src utils”).
Optional options
DEPLOY_MODE: Deployment mode, eithercontainerorvenv(not recommended). Incontainermode, the package is designed to run inside a Docker container. Invenvmode, the package creates a virtual environment on the target system (Backwards compatible with older MCX images). (Default:container).VERSION: The version of the package (default: from default config).ARCHITECTURE: The architecture of the package (default: from default config).MAINTAINER: The maintainer of the package (default: from default config).DESCRIPTION: A description of the package (default: from default config).SERVICE: The systemd service name (default: same as PACKAGE_NAME).BUILDFOLDER: The build folder path (default: from default config).VENV_REQ_DIR: Directory for virtual environment requirements (default: from default config).VENV_TARGET_DIR: Target directory for the virtual environment (default: “/usr/local/.venv.{PACKAGE_NAME}").REQUIREMENTS_FILE: Path to the requirements.txt file (default: from default config,nullwill not download any wheels).SERVICE_TEMPLATE: Path to the systemd service template file (default: “/usr/local/bin/template.service.in”).DEBUG_ON: Enable debug output (default: false).
Any missing option will fall back to the default.
Service Template
By default, a generic systemd service template is used to create the service file for your client application. This template is located at /usr/local/bin/template.service.in inside the Docker container used for building the package. If you want to customize the service file, you can provide your own template by specifying the SERVICE_TEMPLATE option in the package_config.json file.
The custom service template should follow the systemd service file format, see Understand systemd service files. The default template can be seen below for reference:
Warning
It is important to note that @DESCRIPTION@ and @EXEC_START@ will be overwritten when creating the debian package, so make sure to keep this in mind when creating your custom template. The environmental variable DEPLOYED is automatically set to true in @EXEC_START@ so when you overwrite it, make sure to include this variable if you want to use the deployed configuration file.
[Unit]
Description=@DESCRIPTION@
After=ethercat.target motorcortex.service
PartOf=motorcortex.service
StartLimitIntervalSec=0
[Service]
Type=simple
ExecStartPre=/bin/sleep 3
ExecStart=@EXEC_START@
WorkingDirectory=/home/admin
User=admin
Restart=always
RestartSec=1
[Install]
WantedBy=motorcortex.service
Make sure you have Docker installed on your system. You can download and install Docker from the official Docker website.
Build a Debian Package Using the Docker Container
To build your client application into a Debian package using Docker, follow these steps. This method ensures a consistent build environment and isolates the build process from your local system.
-
Build the Docker Image
Navigate to thedeployingdirectory and build the Docker image. This image contains all the necessary tools and dependencies for creating the Debian package.cd deploying docker build -t mcx-2025-03-37-deb-builder .This command creates a Docker image tagged as
mcx-2025-03-37-deb-builder, which is based on the Dockerfile in thedeployingdirectory. -
Run the Docker Container to Build the Package
From your project root directory, run the Docker container. This mounts your current project directory into the container and executes the build process using yourpackage_config.jsonconfiguration file.docker run --rm -v "$PWD:/workspace" -w /workspace mcx-2025-03-37-deb-builder package_config.json--rm: Automatically removes the container after execution.-v "$PWD:/workspace": Mounts your project root ($PWD) to/workspaceinside the container.-w /workspace: Sets the working directory inside the container to/workspace.package_config.json: The configuration file passed as an argument to the container (you can specify a different config file name if needed).
The container will process your project files and configuration to generate the Debian package.
-
Locate the Output
After the build completes successfully, the resulting.debpackage will be created in thebuildfolder within your project directory. You can then upload this package to your Motorcortex binaries portal for deployment.
Warning
The container has been tested for Mcx version 2025.03.37 and uses Python 3.10. Other Motorcortex images and Python versions might also work, but are not officially tested.
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 as wheels. With Docker, this process is automated for you and you only have to provide a requirements.txt file and reference it in your package_config.json. Note that the packages will be installed on your motorcortex application when the package is deployed, not during the build process and, thus, the deployment might take a bit longer depending on the number of packages you include.
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 |
Warning
This method is not recommended as it may lead to incompatible Python packages. It is advised to use the Docker method described in the Docker guide. Some Python dependencies rely on C code that needs to be compiled specifically for the Motorcortex environment. The Docker image used in the official deployment method is pre-configured to ensure compatibility with the Motorcortex application environment. For example, numpy, scipy, and pandas are known to cause issues when not built in the correct environment.
Build a Debian Package Without Using Docker
If you still choose to build locally, follow these steps:
-
Configure the Service Template
When building locally, you must specify the path to the systemd service template in yourpackage_config.jsonfile. This ensures the package includes the correct service configuration. Add this option to your config file to point to the template located in theDocker-builddirectory:"SERVICE_TEMPLATE": "Docker-build/template.service.in" -
Run the Build Script
Execute themakeDeb.pyscript directly on your host system. This script handles the creation of the Debian package structure and includes your Python modules and dependencies.python3 deploying/makeDeb.py package_config.jsonReplace
package_config.jsonwith the path to your configuration file if it’s named differently.
The template contains instructions for Copilot to use, so Copilot can help you code faster with AI. It knows precisely how the client app works and how it can interact with the Motorcortex controller. For the best results, be concrete in your prompts and provide as much context as possible. If you want a parameter ask for it directly and if it is an input or output in the perspective of the client app. Try to build an app in steps, starting with a basic version and then adding features in subsequent prompts to not overwhelm the AI.
Warning
Copilot and similar AI tools can be very helpful to speed up development, but they can also introduce errors or insecure code if not used carefully. Thus, always review and test the generated code thoroughly before deploying it in a production environment.
Copilot looks automatically for instructions in the .github folder that is added in the Template. This folder contains a file called copilot-instructions.md with detailed instructions on how to use Copilot to create Motorcortex client applications. Even though the instructions are quite detailed, it is still important to provide context in the prompt itself to get the best results.
Click to show `copilot-instructions.md`
Note Hyperlinks may not work here, but you can view the file directly in the template repository.
Motorcortex Client Application (MCX-Client-App) - AI Coding Instructions
CRITICAL: READ THESE INSTRUCTIONS COMPLETELY BEFORE GENERATING ANY CODE
This document provides comprehensive servicesdance for AI agents to create, modify, and understand Motorcortex client applications. These are Python applications that connect to a Motorcortex control system to monitor parameters, control robots, automate workflows, and integrate with external systems.
Table of Contents
- How Client Apps Interact with Motorcortex
- Project Overview
- Core Architecture
- Creating an MCX-Client-App
- Configuration System
- Thread vs Non-Thread Usage
- Subscription Patterns
- Error Handler - Triggering System Errors
- Keep iterate() Clean - Best Practices
- Complete Working Examples
- API Reference
- Coding Conventions
How Client Apps Interact with Motorcortex
MCX-Client-Apps connect to a Motorcortex server on a Target via WebSocket (e.g., wss://192.168.1.100) and interact with the parameter tree - a hierarchical structure with existing Motorcortex parameters (like root/AGVControl/actualVelocityLocal, root/Sensors/Temperature) and service-specific parameters.
Operations: Read with self.req.getParameter(), write with self.req.setParameter(), subscribe for real-time updates with self.sub.subscribe().
Adding Custom Service Parameters
CRITICAL: All custom client-app parameters are automatically placed under root/Services/{ServiceName}/serviceParameters/ when defined in the Parameters section of services_config.json.
IMPORTANT: Only add parameters for client-app-specific controls (buttons, counters, settings). If a parameter path is provided like root/AGVControl/actualVelocityLocal or root/Sensors/Temperature, these already exist in the Motorcortex application’s parameter tree - just read/subscribe to them directly. Do NOT document them as “required parameters”.
When your app needs new service parameters, define them in the Parameters section of your service configuration in services_config.json. These parameters will be automatically placed under root/Services/{ServiceName}/serviceParameters/ in the parameter tree.
Service Parameters vs McxClientAppConfiguration
Critical distinction:
Service Parameters (in parameter tree at root/Services/{ServiceName}/serviceParameters/):
- ✅ Use for values that change during runtime (buttons, setpoints, thresholds that users adjust)
- ✅ Can be modified via DESK tool or other clients while app is running
- ✅ Access with
self.req.getParameter(f"{self.options.get_service_parameter_path}/...")or subscriptions - ✅ Changes take effect immediately
- ✅ Defined in the
Parameterssection ofservices_config.json - Example:
{"Name": "MaxSpeed", "Type": "double, input"}under service parameters
McxClientAppConfiguration (in Config section):
- ✅ Use for values that are set once at startup and remain constant
- ✅ Cannot be changed while app is running (requires restart)
- ✅ Access via
self.options.connection_timeout - ✅ Simpler, faster access (no network calls)
- ✅ Defined in the
Configsection ofservices_config.json - Example:
{"connection_timeout": 30}in Config section
Decision Guide:
# ❌ WRONG: Static configuration in service parameters when it never changes at runtime
# (Adds unnecessary complexity and network overhead)
# In services_config.json Parameters section:
{
"Name": "Parameters",
"Children": [
{"Name": "LogFilePath", "Type": "string, parameter", "Value": "/var/log/app.log"}
]
}
# Then reading: self.req.getParameter(f"{self.options.get_service_parameter_path}/LogFilePath")
# ✅ CORRECT: Static configuration in Config section
# In services_config.json Config section: {"log_file_path": "/var/log/app.log", "connection_timeout": 30}
# Access: self.options.log_file_path
# ✅ CORRECT: Runtime-adjustable parameter in service parameters
# In services_config.json Parameters section:
{"Name": "VelocityThresholds", "Type": "double[6], parameter", "Value": [0.2, 0.4, 0.6, 0.8, 1.0, 1.5]}
# Access: self.req.getParameter(f"{self.options.get_service_parameter_path}/VelocityThresholds")
# User can change thresholds in DESK tool, app responds immediately via subscription
Required Service Configuration Format:
Services are configured in services_config.json with the following structure:
{
"Services": [
{
"Name": "RobotController",
"Enabled": true,
"Config": {
"login": "admin",
"password": "your_password",
"target_url": "wss://192.168.1.100",
"autoStart": true
},
"Parameters": {
"Version": "1.0",
"Children": [
{
"Name": "userParameters",
"Children": [
{
"Name": "StartButton",
"Type": "bool, input",
"Value": 0
},
{
"Name": "Counter",
"Type": "int, parameter_volatile",
"Value": 0
}
]
}
]
},
"Watchdog": {
"Enabled": true,
"Disabled": false,
"high": 1000000,
"tooHigh": 5000000
}
}
]
}
Service Configuration Fields:
Name: Service name (used in parameter tree asroot/Services/{Name})Enabled: Whether service is enabledConfig: Runtime configuration (login, password, target_url, autoStart, custom fields) - access viaself.options.field_nameParameters: Service-specific parameters (placed underroot/Services/{Name}/serviceParameters/) - access viaself.options.get_service_parameter_pathWatchdog: Watchdog settings (enabled, thresholds in microseconds)
Application Code:
"""
MCX-Client-App: Robot Controller
"""
import logging
from src.mcx_client_app import McxClientApp, McxClientAppConfiguration
class RobotController(McxClientApp):
def init(self, options: McxClientAppConfiguration):
super().init(options)
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">iterate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#8f5902;font-style:italic"># Access service parameters via self.options.get_service_parameter_path</span>
<span style="color:#000">button</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">getParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_service_parameter_path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">/StartButton"</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_service_parameter_path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">/Counter"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">42</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">1.0</span><span style="color:#000;font-weight:bold">)</span>
if name == "main":
config = McxClientAppConfiguration(name="RobotController")
config.set_config_paths(
deployed_config="/etc/motorcortex/config/services/services_config.json",
non_deployed_config="services_config.json"
)
config.load_config()
app = RobotController(config)
app.run()
Note: Replace "services/RobotController" with your actual client app name for UserParameters organization, and use "robot_controller.json" (lowercase with underscores) as the config filename in the portal.
Parameter Types Reference
Parameter types follow the format: <type>[array_size], <access_level>
Base Types: bool, int, float, double, string
Access Levels (from Target’s perspective):
input: Writable from client apps - client writes, Target reads (buttons, commands, setpoints that control the Target)output: Read-only from client apps - Target writes, client reads (sensor data, status values from Target)parameter: Configuration values (writable but typically set once during setup)parameter_volatile: RECOMMENDED for client app outputs - Values that change frequently at runtime, written by client apps and read by other components
CRITICAL: Access levels are from the Target’s point of view, not the client app:
- If your client app outputs a value to the tree → use
parameter_volatile(optimized for frequent updates from client) - If your client app reads a value from the tree → use
output(Target outputs data to client) - For buttons/commands that control the Target → use
input(Target receives input from client)
Client Apps Can Only Write To:
- ✅
inputparameters - Client can callself.req.setParameter() - ✅
parameterparameters - Client can callself.req.setParameter() - ✅
parameter_volatileparameters - Client can callself.req.setParameter()(RECOMMENDED for client outputs) - ❌
outputparameters - CANNOT write! These are read-only from client perspective
Common Mistake:
# ❌ WRONG: Declaring client app output as "output" type
{"Name": "SafetyZone", "Type": "int, output", "Value": 0}
self.req.setParameter("root/.../SafetyZone", zone).get() # FAILS! Can't write to output
# ✅ CORRECT: Client app output should be "parameter_volatile" type (optimized for client writes)
{"Name": "SafetyZone", "Type": "int, parameter_volatile", "Value": 0}
self.req.setParameter("root/…/SafetyZone", zone).get() # Works! Best performance
Array Syntax: Add [N] for arrays (e.g., double[5], input)
Examples:
bool, input→ Client writes, Target reads: Button press0or1int[3], input→ Client writes array:[1, 2, 3]double, output→ Target writes, client reads: Sensor value42.5string, parameter→ Configuration:"default_name"int, parameter_volatile→ Client app output that updates frequently: Counter value42
Best Practices
✅ DO:
-
Document required parameters in file docstring
-
Use descriptive names, organize hierarchically under
UserParameters -
Use
parameter_volatiletype for client app outputs (data that changes frequently, written by client) -
Use
inputtype for control commands (buttons, commands that trigger actions on Target) -
Use
outputtype for Target data (data flowing FROM Target TO client) -
Group related parameters as arrays instead of separate entries:
// ❌ BAD: Multiple separate threshold parameters {"Name": "Zone1Threshold", "Type": "double, parameter", "Value": 0.2} {"Name": "Zone2Threshold", "Type": "double, parameter", "Value": 0.4}// ✅ GOOD: Single array parameter {"Name": "ZoneThresholds", "Type": "double[6], parameter", "Value": [0.2, 0.4, 0.6, 0.8, 1.0, 1.5]}
❌ DON’T:
- Assume parameters exist, use generic names like
param1 - Create custom parameters directly under
root/- ALL custom params MUST be underroot/UserParameters/ - Use
outputtype for values your client app writes (useparameter_volatileinstead) - Use
inputfor frequently changing client outputs (useparameter_volatilefor better performance) - Try to call
setParameter()onoutputtype parameters (will fail)
Accessing Parameters in Code
Service parameters are automatically scoped to your service and accessed via self.options.get_service_parameter_path:
class MyApp(McxClientApp):
def iterate(self):
# ✅ CORRECT: Access service parameters using get_service_parameter_path
button = self.req.getParameter(f"{self.options.get_service_parameter_path}/StartButton").get().value[0]
self.req.setParameter(f"{self.options.get_service_parameter_path}/Counter", 42).get()
<span style="color:#8f5902;font-style:italic"># ✅ CORRECT: Access existing Motorcortex parameters directly</span>
<span style="color:#000">velocity</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">getParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"root/AGVControl/actualVelocityLocal"</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span>
❌ WRONG - Hardcoded service parameter paths:
def iterate(self):
# ❌ Hardcoded paths - don't do this!
value = self.req.getParameter("root/Services/MyApp/serviceParameters/userParameters/StartButton").get().value[0]
Parameter Access Patterns:
# Read service parameter (any type)
value = self.req.getParameter(f"{self.options.get_service_parameter_path}/StartButton").get().value[0]
# Write service parameter (only works on "input" and "parameter" types!)
self.req.setParameter(f"{self.options.get_service_parameter_path}/Counter", 42).get() # ✅ OK if Counter is "input"
# Array parameter
speeds = self.req.getParameter(f"{self.options.get_service_parameter_path}/Speeds").get().value
# Returns: [0.1, 0.2, 0.3, …]
# Subscribe for real-time updates
self.sub.subscribe(
[f"{self.options.get_service_parameter_path}/StartButton"],
group_alias="btn"
).get().notify(self._onButtonChange)
Why This Matters:
- ✅ Parameters automatically scoped to your service
- ✅ No naming conflicts with other services
- ✅ Cleaner, more maintainable code
- ✅ Follows framework best practices
Example: Complete Documentation
"""
MCX-Client-App: Pick and Place Robot Controller
REQUIRED SERVICE CONFIGURATION:
Add this service to services_config.json:
{
"Name": "PickPlaceController",
"Enabled": true,
"Parameters": {
"Version": "1.0",
"Children": [
{
"Name": "userParameters",
"Children": [
{
"Name": "StartButton",
"Type": "bool, input",
"Value": 0
},
{
"Name": "ResetButton",
"Type": "bool, input",
"Value": 0
},
{
"Name": "CycleCounter",
"Type": "int, parameter_volatile",
"Value": 0
}
]
}
]
}
}
ACCESS IN CODE:
# Read: self.req.getParameter(f"{self.options.get_service_parameter_path}/StartButton").get().value[0]
# Write: self.req.setParameter(f"{self.options.get_service_parameter_path}/CycleCounter", 42).get()
# Subscribe: self.sub.subscribe([f"{self.options.get_service_parameter_path}/StartButton"], "alias").get().notify(callback)
"""
Project Overview
The MCX-Client-App template provides a framework for creating Python applications that:
- Connect to Motorcortex servers via WebSocket (using
motorcortex-pythonlibrary) - Control robots using the
robot_controlAPI - Monitor and set parameters in real-time
- Execute automated workflows with start/stop control
- Deploy as Debian packages on MCX-RTOS systems
Key Files:
- mcx-client-app.py - Main template file (modify this)
- src/mcx_client_app/McxClientApp.py - Base classes (do not modify)
- src/mcx_client_app/McxClientAppConfiguration.py - Configuration class (extend if needed)
- config.json - Runtime configuration (connection settings, custom config)
- service_config.json - Service/deployment configuration (package name, script name)
CRITICAL: Always Update Configuration Files When Making Changes
When you modify the project structure, immediately update the relevant configuration files:
-
Rename/create Python script → Update
service_config.json:{ "PACKAGE_NAME": "my-client-app", "PYTHON_SCRIPT": "my_new_script.py", // ← Update when renaming mcx-client-app.py "PYTHON_MODULES": "src", "DEBUG_ON": false } -
Add custom configuration fields → Update
config.json:{ "login": "", "password": "", "target_url": "wss://192.168.1.100", "cert": "mcx.cert.crt", "my_custom_field": 123 // ← Add your custom fields here } -
Change class name → Update
if __name__ == "__main__":block:if __name__ == "__main__": client_options = CustomMcxClientAppConfiguration.from_json("config.json") app = RobotController(client_options) # ← Update this line app.run()
Common scenarios requiring config updates:
- Creating a new main script file → Update
PYTHON_SCRIPTin service_config.json - Changing package name for deployment → Update
PACKAGE_NAMEin service_config.json - Adding custom configuration options → Add fields to config.json AND custom Configuration class
- Renaming the main Python file → Update
PYTHON_SCRIPTin service_config.json
Core Architecture
Base Classes
McxClientApp - Main thread execution
from src.mcx_client_app import McxClientApp, McxClientAppConfiguration
class MyApp(McxClientApp):
def iterate(self) -> None:
"""Runs in main thread, blocking execution"""
pass
McxClientAppThread - Separate thread execution
from src.mcx_client_app import McxClientAppThread, McxClientAppConfiguration
class MyApp(McxClientAppThread):
def iterate(self) -> None:
"""Runs in separate thread, main thread monitors start/stop"""
pass
Lifecycle Methods
Override these methods in your subclass:
__init__(self, options: McxClientAppConfiguration)- Initialize instance variablesstartOp(self) -> None- Called after connection, before iterate starts (setup subscriptions here)iterate(self) -> None- Main application logic (called repeatedly while running)onExit(self) -> None- Cleanup before disconnect (unsubscribe, save data, etc.)
Inherited Attributes (Available in Your Class)
self.req # motorcortex.Request - for setting/getting parameters
self.sub # motorcortex.Subscription - for subscribing to parameters
self.parameter_tree # motorcortex.ParameterTree - parameter structure
self.motorcortex_types # motorcortex.MessageTypes - message type definitions
self.options # McxClientAppConfiguration - your configuration
self.running # ThreadSafeValue[bool] - current running state
self.watchdog # McxWatchdog - watchdog manager (automatically kept alive)
self.errorHandler # McxErrorHandler - error handler for triggering system errors
CRITICAL - Watchdog Best Practices:
The watchdog is automatically managed and kept alive when you use self.wait() or self.wait_for() methods.
✅ DO:
- Use
self.wait()for delays - keeps watchdog alive automatically - Use
self.wait_for()for waiting on conditions - keeps watchdog alive - Keep
iterate()free of long blocking operations
❌ DON’T:
- Use
time.sleep()- watchdog will timeout! - Block in
iterate()without callingself.wait() - Perform long computations without periodic
self.wait()calls
Example - Correct Watchdog Usage:
def iterate(self):
# ✅ CORRECT: Uses self.wait() which keeps watchdog alive
self.process_data()
self.wait(1.0) # Automatically keeps watchdog alive
<span style="color:#8f5902;font-style:italic"># ✅ CORRECT: For long operations, add periodic waits</span>
<span style="color:#204a87;font-weight:bold">for</span> <span style="color:#000">i</span> <span style="color:#204a87;font-weight:bold">in</span> <span style="color:#204a87">range</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">1000</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">heavy_computation</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">i</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">i</span> <span style="color:#ce5c00;font-weight:bold">%</span> <span style="color:#0000cf;font-weight:bold">100</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">0.01</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#8f5902;font-style:italic"># Keep watchdog alive during long loop</span>
# ❌ WRONG: time.sleep() doesn't keep watchdog alive
def iterate(self):
import time
time.sleep(1.0) # WATCHDOG WILL TIMEOUT!
Error Handler Usage:
The self.errorHandler provides methods to trigger system-level errors at different severity levels:
# In startOp(), optionally configure error handler
def startOp(self):
self.errorHandler.set_subsystem_id(1) # Optional: identify subsystem
self.errorHandler.set_acknowledge_callback(self.on_error_acknowledged)
# Trigger errors at different levels
def iterate(self):
if critical_condition:
self.errorHandler.trigger_emergency_stop(error_code=5001) # Most severe
elif shutdown_needed:
self.errorHandler.trigger_shutdown(error_code=4001)
elif forced_disengage:
self.errorHandler.trigger_forced_disengage(error_code=3001)
elif warning_condition:
self.errorHandler.trigger_warning(error_code=1001) # Least severe
# Optional callback when error is acknowledged
def on_error_acknowledged(self):
logging.info("Error was acknowledged by user")
# Reset your application state here
Error Levels (from MotorcortexErrorLevel enum):
ERROR_LEVEL_UNDEFINED(0): UndefinedINFO(1): Information messageWARNING(2): Warning - does not stop systemFORCED_DISENGAGE(3): Graceful software stopSHUTDOWN(4): Abrupt software stopEMERGENCY_STOP(5): Abrupt software and hardware stop
IMPORTANT: To check subscription status, import motorcortex.OK directly:
import motorcortex
# ✅ CORRECT: Use motorcortex.OK (module-level constant)
if result and result.status == motorcortex.OK:
subscription.notify(callback)
# ❌ WRONG: self.motorcortex_types.OK does not exist
if result and result.status == self.motorcortex_types.OK: # AttributeError!
Inherited Methods (Available in Your Class)
self.wait(timeout: float = 30, testinterval: float = 0.2,
keep_watchdog: bool = True, block_stop_signal: bool = False) -> bool
# Wait for timeout seconds, checking for stop signal. Raises StopSignal when stopped.
# keep_watchdog: If True (default), automatically keeps watchdog alive during wait
# block_stop_signal: If True, ignore stop signals during this wait
self.wait_for(param: str, value: object, index: int = 0, timeout: float = 30,
testinterval: float = 0.2, operat: str = "==",
keep_watchdog: bool = True, block_stop_signal: bool = False) -> bool
# Wait for parameter to meet condition. Raises StopSignal when stopped.
# Operators: "==", "!=", "<", "<=", ">", ">=", "in"
# keep_watchdog: If True (default), automatically keeps watchdog alive while waiting
# block_stop_signal: If True, ignore stop signals during this wait
self.reset() -> None
# Set running flag to False (stops the iterate loop)
Note: Both wait() and wait_for() automatically keep the watchdog alive by default (keep_watchdog=True). This ensures your application doesn’t timeout during normal operations.
Creating an MCX-Client-App
Step 1: Define Your Custom Configuration (Optional)
Only create a custom configuration if you need additional parameters beyond the defaults.
from src.mcx_client_app import McxClientAppConfiguration
class MyAppConfiguration(McxClientAppConfiguration):
"""
Custom configuration with additional parameters.
Attributes:
speed (float): Movement speed in m/s.
cycle_count (int): Number of cycles to execute.
"""
def init(self, speed: float = 0.5, cycle_count: int = 10, **kwargs):
# Set custom attributes BEFORE calling super().init()
self.speed = speed
self.cycle_count = cycle_count
# Always call super().init() LAST with kwargs
super().init(kwargs)
Update services_config.json with your service configuration:
{
"Services": [
{
"Name": "MyApp",
"Enabled": true,
"Config": {
"login": "admin",
"password": "password",
"target_url": "wss://192.168.1.100",
"autoStart": true,
"speed": 0.8,
"cycle_count": 20
},
"Watchdog": {
"Enabled": true,
"Disabled": false,
"high": 1000000,
"tooHigh": 5000000
}
}
]
}
Important Configuration Parameters:
autoStart: Iftrue, app starts immediately. Iffalse, waits forenableServiceparameterWatchdog.Enabled: Enable watchdog monitoring (recommended: true)Watchdog.high: Warning threshold in microsecondsWatchdog.tooHigh: Error threshold in microseconds
Step 2: Create Your Application Class
from src.mcx_client_app import McxClientApp
import logging
class MyApp(McxClientApp):
"""
Brief description of what this application does.
"""
def init(self, options: MyAppConfiguration):
super().init(options)
# Initialize instance variables here
self.counter = 0
self.my_subscription = None
logging.info("MyApp initialized.")
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">startOp</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""
Setup subscriptions and initialize parameters after connection.
This runs once when the application connects to Motorcortex.
"""
# Configure error handler (optional)
self.errorHandler.set_subsystem_id(1)
self.errorHandler.set_acknowledge_callback(self.on_error_acknowledged)
<span style="color:#8f5902;font-style:italic"># Set initial parameter values using service parameter path</span>
<span style="color:#204a87;font-weight:bold">try</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_service_parameter_path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">/Counter"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Counter parameter initialized."</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">except</span> <span style="color:#c00;font-weight:bold">Exception</span> <span style="color:#204a87;font-weight:bold">as</span> <span style="color:#000">e</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">error</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Failed to initialize counter: </span><span style="color:#4e9a06">{</span><span style="color:#000">e</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Setup subscriptions (see Subscription Patterns section)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_setupSubscriptions</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">iterate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""
Main application logic - called repeatedly while running.
Keep this method clean and focused (see Best Practices section).
CRITICAL: Use self.wait() instead of time.sleep() to keep watchdog alive!
"""
# Your main logic here
self.counter += 1
logging.info(f"Iteration {self.counter}")
<span style="color:#8f5902;font-style:italic"># ✅ CORRECT: Use self.wait() - automatically keeps watchdog alive</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">1.0</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#8f5902;font-style:italic"># Wait 1 second</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">on_error_acknowledged</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""
Called when user acknowledges an error (optional).
"""
logging.info("Error acknowledged by user")
# Reset application state here
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">onExit</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""
Cleanup before disconnecting.
"""
# Unsubscribe from subscriptions
if self.my_subscription:
self.my_subscription.unsubscribe()
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Exiting after </span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">counter</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06"> iterations."</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">_setupSubscriptions</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""
Private helper method to setup subscriptions.
Keeps startOp() clean.
"""
pass # Implementation in Subscription Patterns section
# Run the application
if name == "main":
config = MyAppConfiguration.from_json("config.json")
app = MyApp(config)
app.run()
Step 3: Configure and Run
Minimal services_config.json:
{
"Services": [
{
"Name": "MyApp",
"Enabled": true,
"Config": {
"login": "admin",
"password": "password",
"target_url": "wss://localhost",
"autoStart": true
},
"Watchdog": {
"Enabled": true,
"Disabled": false,
"high": 1000000,
"tooHigh": 5000000
}
}
]
}
Main block:
if __name__ == "__main__":
config = MyAppConfiguration(name="MyApp")
config.set_config_paths(
deployed_config="/etc/motorcortex/config/services/services_config.json",
non_deployed_config="services_config.json"
)
config.load_config()
app = MyApp(config)
app.run()
Run locally:
python3 mcx-client-app.py
Configuration System
Built-in Configuration Parameters
The McxClientAppConfiguration class provides these built-in parameters:
McxClientAppConfiguration(
# Connection settings
login: str | None = None, # Username for authentication
password: str | None = None, # Password for authentication
target_url: str = "wss://localhost", # WebSocket URL (local development)
target_url_deployed: str = "wss://localhost", # URL when deployed
cert: str = "mcx.cert.crt", # SSL certificate (local)
cert_deployed: str = "/etc/ssl/certs/mcx.cert.pem", # Certificate (deployed)
<span style="color:#8f5902;font-style:italic"># State management</span>
<span style="color:#000">statecmd_param</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">|</span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"root/Logic/stateCommand"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#8f5902;font-style:italic"># State command parameter</span>
<span style="color:#000">state_param</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">|</span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"root/Logic/state"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#8f5902;font-style:italic"># Current state parameter</span>
<span style="color:#000">run_during_states</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">list</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#8f5902;font-style:italic"># List of State enums when iterate() can run</span>
<span style="color:#8f5902;font-style:italic"># Start/stop control</span>
<span style="color:#000">start_stop_param</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">|</span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#8f5902;font-style:italic"># Parameter to monitor for start/stop (e.g., button)</span>
)
Automatic Deployment Detection
The configuration automatically detects deployment via CONFIG_PATH environment variable:
- Local development: Uses
target_urlandcert - Deployed: Uses
target_url_deployedandcert_deployed, loads fromCONFIG_PATH
Using State-Based Execution
Control when iterate() runs based on Motorcortex system state:
from src.mcx_client_app import State
config = MyAppConfiguration(
target_url="wss://192.168.1.100",
run_during_states=[State.ENGAGED_S, State.IDLE_S], # Only run in these states
)
If run_during_states is empty or None, the app runs in any state.
Using Start/Stop Parameters
The framework automatically manages service enable/disable via the enableService parameter under root/Services/{ServiceName}/enableService.
To control when your app runs:
- Set
autoStart: truein Config → app starts immediately when service is enabled - Set
autoStart: falsein Config → app waits forenableServiceparameter to be set to 1
# In services_config.json:
{
"Config": {
"autoStart": false # Wait for manual enable
}
}
Thread vs Non-Thread Usage
Use McxClientApp (Main Thread) When:
✅ Simple sequential workflows
class SequentialApp(McxClientApp):
def iterate(self):
self.step1()
self.wait(1)
self.step2()
self.wait(1)
✅ Direct control needed - You want the main thread to execute your logic ✅ Simpler debugging - Single-threaded execution is easier to trace ✅ No concurrent operations - No need for parallel monitoring
Behavior:
iterate()runs in main thread- Stop signal checked in
wait()andwait_for()calls - Blocking execution
Use McxClientAppThread (Separate Thread) When:
✅ Long-running operations that should be independently stoppable
class LongRunningApp(McxClientAppThread):
def iterate(self):
# Long computation or operation
for i in range(1000):
self.process_data(i)
if not self.running.get(): # Check manually in loops
break
✅ Independent monitoring - Main thread monitors start/stop while iterate() runs ✅ Responsive stopping - Need immediate response to stop signals without waiting for iterate() to complete
Behavior:
iterate()runs in separate daemon thread- Main thread monitors
runningstate independently - Thread stops when
runningbecomesFalse
IMPORTANT: When using McxClientAppThread, check self.running.get() in long loops:
def iterate(self):
while self.running.get(): # Check running state
# Do work
self.wait(0.1)
Decision Matrix
| Scenario | Use | Reason |
|---|---|---|
| Sequential steps with waits | McxClientApp |
Simple, clear flow |
| Robot motion programs | McxClientApp |
Sequential motion commands |
| Data logging every N seconds | McxClientApp |
Simple periodic task |
| Long computation | McxClientAppThread |
Can stop mid-computation |
| Real-time monitoring | McxClientAppThread |
Independent monitoring |
| Complex state machines | McxClientApp |
Easier to debug |
Default recommendation: Start with McxClientApp unless you have a specific need for threading.
Subscription Patterns
Subscriptions allow you to receive real-time parameter updates from Motorcortex.
Pattern 1: Basic Parameter Subscription
def startOp(self) -> None:
"""Subscribe to a single parameter."""
self.button_subscription = self.sub.subscribe(
["root/UserParameters/Buttons/ResetButton"],
group_alias="reset_button_group",
frq_divider=10 # Update every 10 cycles
)
<span style="color:#000">result</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">button_subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">result</span> <span style="color:#204a87;font-weight:bold">and</span> <span style="color:#000">result</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">status</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#000">motorcortex</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">OK</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">button_subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">notify</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_onButtonUpdate</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">else</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">error</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Failed to subscribe to reset button"</span><span style="color:#000;font-weight:bold">)</span>
def _onButtonUpdate(self, msg) -> None:
"""
Callback for button updates (runs in subscription thread).
Keep this method fast and thread-safe.
"""
button_value = msg[0].value[0]
if button_value != 0:
logging.info("Reset button pressed!")
self.reset_requested.set(True) # Use ThreadSafeValue
Pattern 2: Multiple Parameters in One Subscription
def startOp(self) -> None:
"""Subscribe to multiple related parameters efficiently."""
self.sensor_subscription = self.sub.subscribe(
[
"root/Sensors/Temperature",
"root/Sensors/Pressure",
"root/Sensors/Humidity"
],
group_alias="sensor_group",
frq_divider=100
)
<span style="color:#000">result</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sensor_subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">result</span> <span style="color:#204a87;font-weight:bold">and</span> <span style="color:#000">result</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">status</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#000">motorcortex</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">OK</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sensor_subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">notify</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_onSensorUpdate</span><span style="color:#000;font-weight:bold">)</span>
def _onSensorUpdate(self, msg) -> None:
"""
Callback receives list of messages in same order as subscription.
msg[0] = Temperature, msg[1] = Pressure, msg[2] = Humidity
"""
temp = msg[0].value[0]
pressure = msg[1].value[0]
humidity = msg[2].value[0]
<span style="color:#8f5902;font-style:italic"># Store in thread-safe containers</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">latest_temperature</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">temp</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">latest_pressure</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">pressure</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">latest_humidity</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">humidity</span><span style="color:#000;font-weight:bold">)</span>
Pattern 3: Using ThreadSafeValue for Subscription Data
from src.mcx_client_app import ThreadSafeValue
class MyApp(McxClientApp):
def init(self, options):
super().init(options)
# Use ThreadSafeValue for data shared between threads
self.sensor_value = ThreadSafeValue(0.0)
self.alarm_active = ThreadSafeValue(False)
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">startOp</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sub</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">subscribe</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000;font-weight:bold">[</span><span style="color:#4e9a06">"root/Sensors/Value"</span><span style="color:#000;font-weight:bold">],</span>
<span style="color:#000">group_alias</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#4e9a06">"sensor"</span>
<span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">notify</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_onSensorUpdate</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">_onSensorUpdate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">msg</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#4e9a06">"""Runs in subscription thread"""</span>
<span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">msg</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sensor_value</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#8f5902;font-style:italic"># Thread-safe write</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold">></span> <span style="color:#0000cf;font-weight:bold">100</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">alarm_active</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set</span><span style="color:#000;font-weight:bold">(</span><span style="color:#204a87;font-weight:bold">True</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">iterate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#4e9a06">"""Runs in main thread"""</span>
<span style="color:#000">current</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sensor_value</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#8f5902;font-style:italic"># Thread-safe read</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">alarm_active</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">():</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">warning</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Alarm! Sensor value: </span><span style="color:#4e9a06">{</span><span style="color:#000">current</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">alarm_active</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set</span><span style="color:#000;font-weight:bold">(</span><span style="color:#204a87;font-weight:bold">False</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">1</span><span style="color:#000;font-weight:bold">)</span>
Pattern 4: Unsubscribing in onExit
def onExit(self) -> None:
"""Always unsubscribe to clean up resources."""
if self.button_subscription:
try:
self.button_subscription.unsubscribe()
logging.info("Unsubscribed from button")
except Exception as e:
logging.error(f"Error unsubscribing: {e}")
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sensor_subscription</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#204a87;font-weight:bold">try</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sensor_subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">unsubscribe</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Unsubscribed from sensors"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">except</span> <span style="color:#c00;font-weight:bold">Exception</span> <span style="color:#204a87;font-weight:bold">as</span> <span style="color:#000">e</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">error</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Error unsubscribing: </span><span style="color:#4e9a06">{</span><span style="color:#000">e</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
Subscription Best Practices
- Subscribe in
startOp(), not__init__()- Connection must be established first - Unsubscribe in
onExit()- Clean up resources - Keep callbacks EXTREMELY fast - Callbacks run in subscription thread, don’t block
- Use ThreadSafeValue - For data shared between subscription callbacks and iterate()
- Check subscription result - Always verify
result.status == motorcortex.OK(import motorcortex first) - GROUP parameters in subscriptions whenever possible - More efficient, better performance
- Use descriptive group_alias - Helps with debugging
- Set appropriate frq_divider - Don’t request updates faster than needed (saves bandwidth)
IMPORTANT: Group Subscriptions Together for Better Performance
Strongly Recommended: Subscribe to multiple related parameters in a single subscription instead of creating separate subscriptions. Grouped subscriptions are more efficient and reduce network overhead.
When to Group Parameters:
✅ DO group when:
- Parameters are logically related (all sensor readings, all configuration values)
- Parameters need the same update frequency (same
frq_divider) - Parameters are processed together in your logic
- Reading from the same subsystem (e.g., all from
root/Sensors/, all fromroot/AGVControl/)
❌ DON’T group when:
- Parameters need different update frequencies (one needs 10Hz, another 100Hz)
- One is critical, low-latency and others are not
- Parameters are completely unrelated and processed separately
- Mixing input parameters (you write) with output parameters (you read) that have different purposes
Examples:
✅ GOOD - Grouped subscription (preferred):
def startOp(self) -> None:
"""Group all related sensor readings in ONE subscription."""
self.sensor_subscription = self.sub.subscribe(
[
"root/Sensors/Temperature",
"root/Sensors/Pressure",
"root/Sensors/Humidity",
"root/Sensors/VibrationLevel"
],
group_alias="all_sensors", # One subscription for all sensors
frq_divider=100
)
result = self.sensor_subscription.get()
if result and result.status == motorcortex.OK:
self.sensor_subscription.notify(self._onSensorUpdate)
def _onSensorUpdate(self, msg) -> None:
"""Single callback handles all sensors efficiently."""
self.temperature.set(msg[0].value[0])
self.pressure.set(msg[1].value[0])
self.humidity.set(msg[2].value[0])
self.vibration.set(msg[3].value[0])
❌ BAD - Separate subscriptions (inefficient):
def startOp(self) -> None:
"""Don't do this - wastes resources with 4 separate subscriptions!"""
# ❌ Creates 4 separate network subscriptions - inefficient!
self.sub.subscribe(["root/Sensors/Temperature"], "temp", 100).get().notify(self._onTemp)
self.sub.subscribe(["root/Sensors/Pressure"], "press", 100).get().notify(self._onPress)
self.sub.subscribe(["root/Sensors/Humidity"], "humid", 100).get().notify(self._onHumid)
self.sub.subscribe(["root/Sensors/VibrationLevel"], "vib", 100).get().notify(self._onVib)
# This creates 4x network overhead and 4 separate callbacks!
✅ GOOD - Separate subscriptions when needed:
def startOp(self) -> None:
"""OK to separate when update frequencies differ."""
# High-frequency critical parameter (10Hz)
self.velocity_sub = self.sub.subscribe(
["root/AGVControl/actualVelocityLocal"],
group_alias="velocity",
frq_divider=10 # Fast updates
).get().notify(self._onVelocityUpdate)
<span style="color:#8f5902;font-style:italic"># Low-frequency configuration parameters (1Hz)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">config_sub</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sub</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">subscribe</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000;font-weight:bold">[</span>
<span style="color:#4e9a06">"root/UserParameters/Config/MaxSpeed"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#4e9a06">"root/UserParameters/Config/Thresholds"</span>
<span style="color:#000;font-weight:bold">],</span>
<span style="color:#000">group_alias</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#4e9a06">"config"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">frq_divider</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">1000</span> <span style="color:#8f5902;font-style:italic"># Slow updates - different frequency!</span>
<span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">notify</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_onConfigUpdate</span><span style="color:#000;font-weight:bold">)</span>
Benefits of Grouping:
- ✅ Reduced network overhead - One subscription vs multiple
- ✅ Better performance - Less communication with Motorcortex
- ✅ Simpler code - One callback instead of many
- ✅ Atomic updates - All values update together in one message
- ✅ Easier debugging - Single subscription to monitor
Rule of Thumb: If parameters are updated at the same rate and processed together, always group them in one subscription.
⚠️ CRITICAL: Subscription Callbacks Must Be FAST - They Run on EVERY Tree Update!
🚨 EXTREMELY IMPORTANT: Subscription callbacks fire on EVERY parameter tree update (based on frq_divider), NOT when values change. If frq_divider=10 and Motorcortex runs at 1000Hz, your callback runs 100 times per second - even if the value never changes!
What This Means:
- Your callback executes constantly at high frequency
- The value might be identical on every call
- Callbacks run in a separate thread from your main iterate() loop
- Any blocking operation (file I/O, network calls, complex math) will cause severe performance issues
Golden Rules for Callbacks:
✅ ONLY DO THIS in callbacks:
- Extract value from
msg[0].value[0] - Store it in a ThreadSafeValue:
self.my_value.set(value) - (Optional) Check if changed:
if value != self.previous_value - That’s it. Nothing else.
❌ NEVER DO THIS in callbacks:
- ❌ Logging on every call (spams logs at 100Hz+)
- ❌ Call
self.req.setParameter()(network overhead every cycle!) - ❌ File I/O (writing to files, reading config)
- ❌ Complex calculations (trigonometry, matrix math)
- ❌ Database queries
- ❌ Sleep/wait calls
- ❌ Calling other slow functions
Callback Best Practices:
✅ DO - Minimal, fast callback:
def _onVelocityUpdate(self, msg) -> None:
"""GOOD: Fast extraction and storage only."""
new_velocity = msg[0].value[0]
self.current_velocity.set(new_velocity) # Just store it
# Done! Let iterate() handle the rest
✅ DO - With change detection (still fast):
def _onVelocityUpdate(self, msg) -> None:
"""GOOD: Quick check if changed, minimal logging."""
new_velocity = msg[0].value[0]
self.current_velocity.set(new_velocity)
<span style="color:#8f5902;font-style:italic"># Only log when value actually changes (not every tree update)</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">new_velocity</span> <span style="color:#ce5c00;font-weight:bold">!=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">previous_velocity</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Velocity changed: </span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">previous_velocity</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06"> → </span><span style="color:#4e9a06">{</span><span style="color:#000">new_velocity</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">previous_velocity</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">new_velocity</span>
❌ DON’T - Slow operations in callback:
def _onVelocityUpdate(self, msg) -> None:
"""BAD: This runs 100+ times per second!"""
velocity = msg[0].value[0]
<span style="color:#8f5902;font-style:italic"># ❌ Logs EVERY tree update (even if value unchanged) - log spam!</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Velocity: </span><span style="color:#4e9a06">{</span><span style="color:#000">velocity</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># ❌ Network call EVERY cycle - huge overhead!</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"root/Status/LastVelocity"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">velocity</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#8f5902;font-style:italic"># ❌ File I/O EVERY cycle - kills performance!</span>
<span style="color:#204a87;font-weight:bold">with</span> <span style="color:#204a87">open</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"velocity_log.txt"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#4e9a06">"a"</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#204a87;font-weight:bold">as</span> <span style="color:#000">f</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">f</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">write</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"</span><span style="color:#4e9a06">{</span><span style="color:#000">velocity</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">\n</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># ❌ Complex math EVERY cycle - wastes CPU!</span>
<span style="color:#000">safety_zone</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_calculateComplexSafetyZone</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">velocity</span><span style="color:#000;font-weight:bold">)</span>
✅ CORRECT Pattern - Callback stores, iterate() processes:
def __init__(self, options):
super().__init__(options)
self.current_velocity = ThreadSafeValue(0.0)
self.previous_velocity = 0.0
def _onVelocityUpdate(self, msg) -> None:
"""Callback: ONLY extract and store. Runs 100+ times/sec!"""
new_velocity = msg[0].value[0]
self.current_velocity.set(new_velocity) # Fast storage only
def iterate(self) -> None:
"""iterate(): Do ALL the processing here. Runs at your control rate."""
velocity = self.current_velocity.get()
<span style="color:#8f5902;font-style:italic"># Do expensive operations in iterate(), not callback</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">velocity</span> <span style="color:#ce5c00;font-weight:bold">!=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">previous_velocity</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#8f5902;font-style:italic"># Log changes</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Velocity: </span><span style="color:#4e9a06">{</span><span style="color:#000">velocity</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Update parameters</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"root/Status/LastVelocity"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">velocity</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#8f5902;font-style:italic"># Complex calculations</span>
<span style="color:#000">safety_zone</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_calculateComplexSafetyZone</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">velocity</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># File I/O</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_logToFile</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">velocity</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">previous_velocity</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">velocity</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">0.1</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#8f5902;font-style:italic"># Control your own update rate</span>
Summary - Callback Rules:
- Callbacks run at HIGH FREQUENCY (100Hz+) on EVERY tree update
- Keep callbacks to 3 lines max: Extract → Store → (Maybe check if changed)
- Do ALL processing in iterate(): Logging, setParameter, calculations, file I/O
- Remember: Tree update ≠ Value change - Same value repeated constantly!
Error Handler - Triggering System Errors
The McxErrorHandler allows your client application to trigger system-level errors at different severity levels in Motorcortex. This is essential for integrating your application into the overall system safety and error management.
Overview
Every McxClientApp instance has a built-in error handler accessible via self.errorHandler. The error handler:
- Triggers errors at 5 different severity levels (INFO, WARNING, FORCED_DISENGAGE, SHUTDOWN, EMERGENCY_STOP)
- Uses subsystem IDs to identify which service or component triggered the error
- Uses error codes to describe what the error is (e.g., battery low, sensor failure, timeout)
- Supports acknowledgment callbacks to reset your application state when errors are cleared
Error Severity Levels
The error handler supports 5 severity levels (from MotorcortexErrorLevel enum):
| Level | Value | Description | System Response |
|---|---|---|---|
ERROR_LEVEL_UNDEFINED |
0 | Undefined/cleared error | Clears active errors |
INFO |
1 | Information message | No system action, just logging |
WARNING |
2 | Warning condition | System continues running |
FORCED_DISENGAGE |
3 | Graceful software stop | Disengages system gracefully |
SHUTDOWN |
4 | Abrupt software stop | Immediate software shutdown |
EMERGENCY_STOP |
5 | Hardware emergency stop | Software AND hardware stop |
Severity Progression: INFO < WARNING < FORCED_DISENGAGE < SHUTDOWN < EMERGENCY_STOP
Subsystem IDs - Identifying Error Sources
Subsystem IDs identify which service or component triggered an error. This is critical for diagnosing issues in complex systems with multiple services.
Best Practices:
✅ One Subsystem ID per Service:
- If your client app is a single logical service (e.g., “BatteryMonitor”), use one subsystem ID for the entire service
- Set the subsystem ID in
startOp()and use it for all errors in that service
def startOp(self) -> None:
# Set subsystem ID for this service
self.errorHandler.set_subsystem_id(1) # BatteryMonitor = subsystem 1
✅ Multiple Subsystem IDs within a Service:
- If your service has distinct subsystems (e.g., “FleetManager” managing multiple robots), use different IDs for each
- Pass
subsystem_idparameter when triggering errors to distinguish between subsystems
class FleetManager(McxClientApp):
def __init__(self, options):
super().__init__(options)
self.ROBOT_1_SUBSYSTEM = 10
self.ROBOT_2_SUBSYSTEM = 11
self.ROBOT_3_SUBSYSTEM = 12
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">check_robot_1</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">battery_low</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#8f5902;font-style:italic"># Specify which robot has the error</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_warning</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">1001</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">subsystem_id</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">ROBOT_1_SUBSYSTEM</span>
<span style="color:#000;font-weight:bold">)</span>
Subsystem ID Guidelines:
- Use unique IDs across your entire system to avoid conflicts
- Document your subsystem ID allocation (e.g., 1-10 for service A, 11-20 for service B)
- Reserve ID 0 for system-level errors (undefined subsystem)
- Use consistent IDs - don’t change them between runs
Error Codes - Describing What Happened
Error codes describe the specific condition that triggered the error. Think of them as unique identifiers for different failure modes.
Best Practices:
✅ Define Error Code Constants:
class BatteryMonitor(McxClientApp):
# Error code definitions
ERROR_BATTERY_LOW = 1001
ERROR_BATTERY_CRITICAL = 1002
ERROR_CHARGING_FAULT = 1003
ERROR_TEMPERATURE_HIGH = 1004
ERROR_COMMUNICATION_LOST = 1005
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">check_battery</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">battery_voltage</span> <span style="color:#ce5c00;font-weight:bold"><</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">CRITICAL_THRESHOLD</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_emergency_stop</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">ERROR_BATTERY_CRITICAL</span>
<span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#000">battery_voltage</span> <span style="color:#ce5c00;font-weight:bold"><</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">LOW_THRESHOLD</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_warning</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">ERROR_BATTERY_LOW</span>
<span style="color:#000;font-weight:bold">)</span>
✅ Use Meaningful Ranges:
# Organize error codes by category
# 1000-1099: Battery errors
# 1100-1199: Sensor errors
# 1200-1299: Communication errors
# 1300-1399: Motion errors
ERROR_BATTERY_LOW = 1001
ERROR_BATTERY_CRITICAL = 1002
ERROR_SENSOR_FAULT = 1101
ERROR_SENSOR_CALIBRATION = 1102
ERROR_COMM_TIMEOUT = 1201
ERROR_COMM_LOST = 1202
✅ Document Error Codes:
"""
BatteryMonitor Service - Error Codes
Subsystem ID: 1
Error Codes:
- 1001: Battery voltage below warning threshold (triggers WARNING)
- 1002: Battery voltage critical (triggers EMERGENCY_STOP)
- 1003: Charging circuit fault detected (triggers FORCED_DISENGAGE)
- 1004: Battery temperature too high (triggers SHUTDOWN)
- 1005: Communication with battery management lost (triggers WARNING)
"""
Basic Usage Pattern
1. Configure Error Handler in startOp():
def startOp(self) -> None:
"""Initialize error handler with subsystem ID."""
# Set subsystem ID for this service
self.errorHandler.set_subsystem_id(1)
<span style="color:#8f5902;font-style:italic"># Optional: Set callback for error acknowledgment</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set_acknowledge_callback</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">on_error_acknowledged</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Error handler configured for subsystem 1"</span><span style="color:#000;font-weight:bold">)</span>
2. Trigger Errors in Your Code:
def iterate(self) -> None:
"""Monitor conditions and trigger appropriate errors."""
battery_voltage = self.get_battery_voltage()
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">battery_voltage</span> <span style="color:#ce5c00;font-weight:bold"><</span> <span style="color:#0000cf;font-weight:bold">10.5</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#8f5902;font-style:italic"># Critical - stop everything immediately</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_emergency_stop</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">1002</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#000">battery_voltage</span> <span style="color:#ce5c00;font-weight:bold"><</span> <span style="color:#0000cf;font-weight:bold">11.0</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#8f5902;font-style:italic"># Low battery warning</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_warning</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">1001</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">1.0</span><span style="color:#000;font-weight:bold">)</span>
3. Handle Error Acknowledgment (Optional):
def on_error_acknowledged(self) -> None:
"""Called when operator acknowledges the error."""
logging.info("Error acknowledged - resetting application state")
<span style="color:#8f5902;font-style:italic"># Reset your error conditions</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">error_count</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">0</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">retry_attempts</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">0</span>
<span style="color:#8f5902;font-style:italic"># Clear any error flags</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#4e9a06">f</span><span style="color:#4e9a06">"</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_service_parameter_path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">/ErrorActive"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#0000cf;font-weight:bold">0</span>
<span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
Complete Error Handler Example
import logging
import motorcortex
from src.mcx_client_app import McxClientApp, McxClientAppConfiguration
class SafetyMonitor(McxClientApp):
"""
Monitors safety conditions and triggers appropriate errors.
Subsystem ID: 5
Error Codes:
- 2001: Temperature warning (>70°C)
- 2002: Temperature critical (>85°C)
- 2003: Pressure out of range
- 2004: Emergency stop button pressed
"""
<span style="color:#8f5902;font-style:italic"># Subsystem ID</span>
<span style="color:#000">SUBSYSTEM_ID</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">5</span>
<span style="color:#8f5902;font-style:italic"># Error codes</span>
<span style="color:#000">ERROR_TEMP_WARNING</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">2001</span>
<span style="color:#000">ERROR_TEMP_CRITICAL</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">2002</span>
<span style="color:#000">ERROR_PRESSURE</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">2003</span>
<span style="color:#000">ERROR_ESTOP</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">2004</span>
<span style="color:#8f5902;font-style:italic"># Thresholds</span>
<span style="color:#000">TEMP_WARNING</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">70.0</span>
<span style="color:#000">TEMP_CRITICAL</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">85.0</span>
<span style="color:#000">PRESSURE_MIN</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">50.0</span>
<span style="color:#000">PRESSURE_MAX</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">150.0</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">options</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000">McxClientAppConfiguration</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87">super</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">options</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">last_temp_state</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"normal"</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">error_count</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">0</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">startOp</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Configure error handler."""</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set_subsystem_id</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">SUBSYSTEM_ID</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set_acknowledge_callback</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">on_error_acknowledged</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Safety monitor configured with subsystem ID </span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">SUBSYSTEM_ID</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">iterate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Monitor safety parameters."""</span>
<span style="color:#8f5902;font-style:italic"># Get sensor values</span>
<span style="color:#000">temperature</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">getParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"root/Sensors/Temperature"</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span>
<span style="color:#000">pressure</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">getParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"root/Sensors/Pressure"</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span>
<span style="color:#000">estop</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">getParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"root/Safety/EmergencyStop"</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span>
<span style="color:#8f5902;font-style:italic"># Check emergency stop (highest priority)</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">estop</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_emergency_stop</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">ERROR_ESTOP</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">critical</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Emergency stop button pressed!"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">return</span>
<span style="color:#8f5902;font-style:italic"># Check temperature (rising severity)</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">temperature</span> <span style="color:#ce5c00;font-weight:bold">></span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">TEMP_CRITICAL</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">last_temp_state</span> <span style="color:#ce5c00;font-weight:bold">!=</span> <span style="color:#4e9a06">"critical"</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_shutdown</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">ERROR_TEMP_CRITICAL</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">error</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Temperature critical: </span><span style="color:#4e9a06">{</span><span style="color:#000">temperature</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">°C"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">last_temp_state</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"critical"</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#000">temperature</span> <span style="color:#ce5c00;font-weight:bold">></span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">TEMP_WARNING</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">last_temp_state</span> <span style="color:#ce5c00;font-weight:bold">!=</span> <span style="color:#4e9a06">"warning"</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_warning</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">ERROR_TEMP_WARNING</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">warning</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Temperature high: </span><span style="color:#4e9a06">{</span><span style="color:#000">temperature</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">°C"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">last_temp_state</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"warning"</span>
<span style="color:#204a87;font-weight:bold">else</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">last_temp_state</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"normal"</span>
<span style="color:#8f5902;font-style:italic"># Check pressure</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">pressure</span> <span style="color:#ce5c00;font-weight:bold"><</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">PRESSURE_MIN</span> <span style="color:#204a87;font-weight:bold">or</span> <span style="color:#000">pressure</span> <span style="color:#ce5c00;font-weight:bold">></span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">PRESSURE_MAX</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_forced_disengage</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">ERROR_PRESSURE</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">error</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Pressure out of range: </span><span style="color:#4e9a06">{</span><span style="color:#000">pressure</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06"> bar"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">1.0</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">on_error_acknowledged</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Reset state when error is acknowledged."""</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Error acknowledged - resetting safety monitor"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">last_temp_state</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"normal"</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">error_count</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">0</span>
if name == "main":
config = McxClientAppConfiguration(name="SafetyMonitor")
config.set_config_paths(
deployed_config="/etc/motorcortex/config/services/services_config.json",
non_deployed_config="services_config.json"
)
config.load_config()
app = SafetyMonitor(config)
app.run()
Advanced Patterns
Rising Edge Detection (Trigger Error Once):
def __init__(self, options):
super().__init__(options)
self.last_value = 0
def iterate(self):
value = self.req.getParameter(f"{self.options.get_service_parameter_path}/Input").get().value[0]
<span style="color:#8f5902;font-style:italic"># Only trigger on change (0 -> non-zero)</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">last_value</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#0000cf;font-weight:bold">0</span> <span style="color:#204a87;font-weight:bold">and</span> <span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold">!=</span> <span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold"><</span> <span style="color:#0000cf;font-weight:bold">10</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_warning</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">3001</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold"><</span> <span style="color:#0000cf;font-weight:bold">20</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_forced_disengage</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">3002</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">last_value</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">value</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">0.5</span><span style="color:#000;font-weight:bold">)</span>
Multi-Subsystem Service:
class MultiRobotController(McxClientApp):
"""Controls 3 robots with separate subsystem IDs."""
<span style="color:#000">ROBOT_A_SUBSYSTEM</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">20</span>
<span style="color:#000">ROBOT_B_SUBSYSTEM</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">21</span>
<span style="color:#000">ROBOT_C_SUBSYSTEM</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">22</span>
<span style="color:#000">ERROR_COLLISION</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">4001</span>
<span style="color:#000">ERROR_TIMEOUT</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">4002</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">check_robot_a</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">collision_detected</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_emergency_stop</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">ERROR_COLLISION</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">subsystem_id</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">ROBOT_A_SUBSYSTEM</span> <span style="color:#8f5902;font-style:italic"># Identify which robot</span>
<span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">check_robot_b</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">motion_timeout</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_warning</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">ERROR_TIMEOUT</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">subsystem_id</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">ROBOT_B_SUBSYSTEM</span>
<span style="color:#000;font-weight:bold">)</span>
Clearing Errors Programmatically:
def iterate(self):
# Check if error condition resolved
if self.error_active and self.error_resolved():
# Clear the error by triggering ERROR_LEVEL_UNDEFINED
self.errorHandler.trigger_error(
level=MotorcortexErrorLevel.ERROR_LEVEL_UNDEFINED,
code=0
)
self.error_active = False
logging.info("Error condition resolved, error cleared")
Error Handler Best Practices
✅ DO:
- Define subsystem IDs as class constants with descriptive names
- Document all error codes in class/module docstring
- Use error code ranges for different error categories (1000-1099: battery, 1100-1199: sensors)
- Implement rising edge detection to avoid repeated error triggers
- Set subsystem ID in
startOp()for single-subsystem services - Use acknowledge callbacks to reset application state
- Log when errors are triggered for debugging
❌ DON’T:
- Use random or changing subsystem IDs - they must be consistent
- Reuse error codes for different conditions
- Trigger errors in subscription callbacks (too fast, wrong thread)
- Forget to document your subsystem ID and error code allocation
- Use subsystem ID 0 unless it’s truly a system-level error
- Trigger EMERGENCY_STOP for non-critical conditions
Error Handler Reference
Available Methods:
# Configure (in startOp)
self.errorHandler.set_subsystem_id(subsystem_id: int)
self.errorHandler.set_acknowledge_callback(callback: Callable)
# Trigger errors (in iterate or other methods)
self.errorHandler.trigger_info(error_code: int, subsystem_id: int = None)
self.errorHandler.trigger_warning(error_code: int, subsystem_id: int = None)
self.errorHandler.trigger_forced_disengage(error_code: int, subsystem_id: int = None)
self.errorHandler.trigger_shutdown(error_code: int, subsystem_id: int = None)
self.errorHandler.trigger_emergency_stop(error_code: int, subsystem_id: int = None)
# Generic trigger (advanced use)
self.errorHandler.trigger_error(
level: MotorcortexErrorLevel,
code: int,
subsystem_id: int = None
)
When to Use Each Severity Level:
- INFO: Non-critical information (state changes, milestones reached)
- WARNING: Conditions that need attention but don’t stop operation (high temperature, low battery warning)
- FORCED_DISENGAGE: Controlled shutdown needed (sensor fault, timeout, safe limits exceeded)
- SHUTDOWN: Immediate stop required (critical sensor failure, communication lost)
- EMERGENCY_STOP: Hardware safety issue (collision detected, physical emergency stop pressed)
Keep iterate() Clean - Best Practices
Rule 1: Extract Logic into Private Methods
❌ BAD - Cluttered iterate():
def iterate(self):
# Long inline logic
temp = self.req.getParameter("root/Sensors/Temp").get().value[0]
if temp > 50:
self.req.setParameter("root/Cooling/Fan", 1).get()
logging.info("Fan activated")
else:
self.req.setParameter("root/Cooling/Fan", 0).get()
logging.info("Fan deactivated")
<span style="color:#000">pressure</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">getParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"root/Sensors/Pressure"</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">pressure</span> <span style="color:#ce5c00;font-weight:bold">></span> <span style="color:#0000cf;font-weight:bold">100</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"root/Safety/Alarm"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">1</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">warning</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Pressure alarm!"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">1</span><span style="color:#000;font-weight:bold">)</span>
✅ GOOD - Clean iterate():
def iterate(self):
"""Main control loop - delegates to helper methods."""
self._checkTemperature()
self._checkPressure()
self.wait(1)
def _checkTemperature(self) -> None:
"""Monitor temperature and control cooling fan."""
temp = self.req.getParameter("root/Sensors/Temp").get().value[0]
fan_state = 1 if temp > 50 else 0
self.req.setParameter("root/Cooling/Fan", fan_state).get()
logging.debug(f"Fan: {fan_state}, Temp: {temp}")
def _checkPressure(self) -> None:
"""Monitor pressure and trigger alarm if needed."""
pressure = self.req.getParameter("root/Sensors/Pressure").get().value[0]
if pressure > 100:
self.req.setParameter("root/Safety/Alarm", 1).get()
logging.warning(f"Pressure alarm: {pressure}")
Rule 2: Use Subscriptions Instead of Polling
❌ BAD - Polling in iterate():
def iterate(self):
button = self.req.getParameter("root/Buttons/Start").get().value[0]
if button != self.last_button_state:
if button == 1:
self._startOperation()
self.last_button_state = button
self.wait(0.1)
✅ GOOD - Use subscriptions:
def startOp(self):
"""Setup subscription once."""
self.button_sub = self.sub.subscribe(
["root/Buttons/Start"],
group_alias="start_button"
).get()
self.button_sub.notify(self._onButtonPress)
def _onButtonPress(self, msg):
"""React to button press immediately."""
if msg[0].value[0] == 1:
self.operation_requested.set(True)
def iterate(self):
"""Check request flag, not polling parameter."""
if self.operation_requested.get():
self._startOperation()
self.operation_requested.set(False)
self.wait(1)
Rule 3: Use State Machines for Complex Logic
✅ GOOD - State machine pattern:
from enum import Enum
class RobotState(Enum):
IDLE = 0
MOVING_TO_START = 1
EXECUTING = 2
RETURNING = 3
class RobotApp(McxClientApp):
def init(self, options):
super().init(options)
self.state = RobotState.IDLE
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">iterate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#4e9a06">"""State machine - clean and readable."""</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">state</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#000">RobotState</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">IDLE</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_handleIdleState</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">state</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#000">RobotState</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">MOVING_TO_START</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_handleMovingState</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">state</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#000">RobotState</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">EXECUTING</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_handleExecutingState</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">state</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#000">RobotState</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">RETURNING</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_handleReturningState</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">0.1</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">_handleIdleState</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">start_requested</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">():</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">state</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">RobotState</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">MOVING_TO_START</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Starting operation"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">_handleMovingState</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_isAtStartPosition</span><span style="color:#000;font-weight:bold">():</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">state</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">RobotState</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">EXECUTING</span>
<span style="color:#8f5902;font-style:italic"># ... other state handlers</span>
Rule 4: Initialize Data in init, Not iterate()
❌ BAD:
def iterate(self):
if not hasattr(self, 'counter'):
self.counter = 0 # Don't do this!
self.counter += 1
✅ GOOD:
def __init__(self, options):
super().__init__(options)
self.counter = 0 # Initialize here
def iterate(self):
self.counter += 1
Rule 5: Use wait() and wait_for() Appropriately
✅ GOOD - Responsive to stop signals:
def iterate(self):
self._doWork()
self.wait(5) # Checks stop signal every 0.2s by default
def _longOperation(self):
# Wait for condition with timeout
success = self.wait_for(
param="root/Operations/Complete",
value=1,
timeout=30,
operat="=="
)
if not success:
logging.error("Operation timeout")
Rule 6: Keep iterate() Focused on One Responsibility
If iterate() does too much, split into multiple applications:
# Instead of one app doing everything:
class MonsterApp(McxClientApp):
def iterate(self):
self._logData()
self._controlRobot()
self._monitorSensors()
self._updateDatabase()
self._sendNotifications()
# Split into multiple focused apps:
class DataLoggerApp(McxClientApp):
def iterate(self):
self._logData()
class RobotControlApp(McxClientApp):
def iterate(self):
self._controlRobot()
Clean iterate() Checklist
- Less than 20 lines in
iterate()method - No parameter access logic (use subscriptions or helper methods)
- No inline conditionals longer than 1 line
- Uses
self.wait()orself.wait_for()for delays - Delegates to private helper methods (prefix with
_) - Clear control flow (state machine if complex)
- No initialization code (use
__init__()orstartOp())
Complete Working Examples
Example 1: Simple Counter with Start/Stop Button
import logging
from src.mcx_client_app import McxClientApp, McxClientAppConfiguration
class CounterApp(McxClientApp):
"""Increments counter when start button is active."""
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">options</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000">McxClientAppConfiguration</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87">super</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">options</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">counter</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">0</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">startOp</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Initialize counter parameter."""</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_service_parameter_path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">/Counter"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Counter initialized"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">iterate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Increment counter every second."""</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">counter</span> <span style="color:#ce5c00;font-weight:bold">+=</span> <span style="color:#0000cf;font-weight:bold">1</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_service_parameter_path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">/Counter"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">counter</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Counter: </span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">counter</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">1</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">onExit</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Log final count."""</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Exiting. Final count: </span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">counter</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
if name == "main":
config = McxClientAppConfiguration(name="CounterApp")
config.set_config_paths(
deployed_config="/etc/motorcortex/config/services/services_config.json",
non_deployed_config="services_config.json"
)
config.load_config()
app = CounterApp(config)
app.run()
Example 2: Temperature Monitor with Alarm
import logging
import motorcortex
from src.mcx_client_app import McxClientApp, McxClientAppConfiguration, ThreadSafeValue
class TemperatureMonitor(McxClientApp):
"""Monitors temperature and activates alarm if threshold exceeded."""
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">options</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000">McxClientAppConfiguration</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87">super</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">options</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">current_temperature</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">ThreadSafeValue</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">0.0</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">alarm_threshold</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">75.0</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">temp_subscription</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">startOp</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Subscribe to temperature parameter."""</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">temp_subscription</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sub</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">subscribe</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000;font-weight:bold">[</span><span style="color:#4e9a06">"root/Sensors/Temperature"</span><span style="color:#000;font-weight:bold">],</span>
<span style="color:#000">group_alias</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#4e9a06">"temperature"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">frq_divider</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">50</span>
<span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">result</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">temp_subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">result</span> <span style="color:#204a87;font-weight:bold">and</span> <span style="color:#000">result</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">status</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#000">motorcortex</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">OK</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">temp_subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">notify</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_onTemperatureUpdate</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Temperature subscription active"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">_onTemperatureUpdate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">msg</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Update current temperature (runs in subscription thread)."""</span>
<span style="color:#000">temp</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">msg</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">current_temperature</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">temp</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">iterate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Check temperature and control alarm."""</span>
<span style="color:#000">temp</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">current_temperature</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">temp</span> <span style="color:#ce5c00;font-weight:bold">></span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">alarm_threshold</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_activateAlarm</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">temp</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">else</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_deactivateAlarm</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">2</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">_activateAlarm</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">temp</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Activate alarm and log warning."""</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"root/Safety/TemperatureAlarm"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">1</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">warning</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Temperature alarm! Current: </span><span style="color:#4e9a06">{</span><span style="color:#000">temp</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">°C, Threshold: </span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">alarm_threshold</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">°C"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">_deactivateAlarm</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Deactivate alarm."""</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"root/Safety/TemperatureAlarm"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">onExit</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Cleanup subscriptions."""</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">temp_subscription</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">temp_subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">unsubscribe</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"root/Safety/TemperatureAlarm"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Temperature monitor stopped"</span><span style="color:#000;font-weight:bold">)</span>
if name == "main":
config = McxClientAppConfiguration(name="TemperatureMonitor")
config.set_config_paths(
deployed_config="/etc/motorcortex/config/services/services_config.json",
non_deployed_config="services_config.json"
)
config.load_config()
app = TemperatureMonitor(config)
app.run()
"""Check temperature and control alarm."""
temp = self.current_temperature.get()
if temp > self.alarm_threshold:
self._activateAlarm(temp)
else:
self._deactivateAlarm()
self.wait(2)
def _activateAlarm(self, temp: float) -> None:
"""Activate alarm and log warning."""
self.req.setParameter("root/Safety/TemperatureAlarm", 1).get()
logging.warning(f"Temperature alarm! Current: {temp}°C, Threshold: {self.alarm_threshold}°C")
def _deactivateAlarm(self) -> None:
"""Deactivate alarm."""
self.req.setParameter("root/Safety/TemperatureAlarm", 0).get()
def onExit(self) -> None:
"""Cleanup subscriptions."""
if self.temp_subscription:
self.temp_subscription.unsubscribe()
self.req.setParameter("root/Safety/TemperatureAlarm", 0).get()
logging.info("Temperature monitor stopped")
if name == “main": config = McxClientAppConfiguration() # Update the config paths below to match your deployment requirements # deployed_config: Path used when DEPLOYED env var is set (on production systems) # non_deployed_config: Path used during local development config.set_config_paths( deployed_config="/etc/motorcortex/config/services/temperature_monitor.json”, non_deployed_config=“config.json” ) app = TemperatureMonitor(config) app.run()
### Example 3: Robot Motion Program
import logging
import math
from src.mcx_client_app import McxClientApp, McxClientAppConfiguration, State
from robot_control.motion_program import MotionProgram, Waypoint
from robot_control.robot_command import RobotCommand
from robot_control.system_defs import InterpreterStates
class RobotPickPlace(McxClientApp):
"""Executes pick-and-place motion program."""
def __init__(self, options: McxClientAppConfiguration):
super().__init__(options)
self.robot = None
self.cycle_count = 0
def startOp(self) -> None:
"""Initialize robot and engage."""
self.robot = RobotCommand(self.req, self.motorcortex_types)
if self.robot.engage():
logging.info("Robot engaged successfully")
self.robot.stop()
self.robot.reset()
else:
logging.error("Failed to engage robot")
self.reset() # Stop the application
def iterate(self) -> None:
"""Execute pick-and-place cycle."""
self._executePickPlace()
self.cycle_count += 1
logging.info(f"Completed cycle {self.cycle_count}")
self.wait(2)
def _executePickPlace(self) -> None:
"""Execute the pick-and-place motion program."""
# Define waypoints
home = Waypoint([0.4, 0.0, 0.35, 0, math.pi, 0])
pick = Waypoint([0.5, 0.2, 0.1, 0, math.pi, 0])
place = Waypoint([0.5, -0.2, 0.1, 0, math.pi, 0])
# Create motion program
mp = MotionProgram(self.req, self.motorcortex_types)
mp.addMoveL([home], velocity=0.3, acceleration=0.5)
mp.addMoveL([pick], velocity=0.2, acceleration=0.3)
mp.addMoveL([place], velocity=0.2, acceleration=0.3)
mp.addMoveL([home], velocity=0.3, acceleration=0.5)
# Send and execute
mp.send("pick_place_cycle").get()
state = self.robot.play()
if state == InterpreterStates.MOTION_NOT_ALLOWED_S.value:
logging.info("Moving to start position...")
if self.robot.moveToStart(10):
self.robot.play()
# Wait for completion
self.wait_for("root/Control/fInterpreterState",
InterpreterStates.MOTION_COMPLETE_S.value,
timeout=30)
def onExit(self) -> None:
"""Stop and disengage robot."""
if self.robot:
self.robot.stop()
self.robot.disengage()
logging.info(f"Robot stopped after {self.cycle_count} cycles")
if __name__ == "__main__":
config = McxClientAppConfiguration()
# Update the config paths below to match your deployment requirements
# deployed_config: Path used when DEPLOYED env var is set (on production systems)
# non_deployed_config: Path used during local development
config.set_config_paths(
deployed_config="/etc/motorcortex/config/services/robot_pick_place.json",
non_deployed_config="config.json"
)
config.run_during_states = [State.ENGAGED_S] # Only run when engaged
app = RobotPickPlace(config)
app.run()
</code></pre><h3 id="example-4-error-handler-with-rising-edge-detection">Example 4: Error Handler with Rising Edge Detection</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">logging</span>
<span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">motorcortex</span>
<span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">src.mcx_client_app</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">McxClientApp</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">McxClientAppConfiguration</span>
<span style="color:#204a87;font-weight:bold">class</span> <span style="color:#000">ErrorHandlerApp</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">McxClientApp</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#4e9a06">"""
</span><span style="color:#4e9a06"> Example demonstrating error handling with different severity levels.
</span><span style="color:#4e9a06"> Monitors input parameter and triggers appropriate errors based on value.
</span><span style="color:#4e9a06"> Uses rising edge detection to trigger errors only once per range entry.
</span><span style="color:#4e9a06"> """</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">options</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000">McxClientAppConfiguration</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87">super</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">options</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">__last_value</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">int</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">0</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">startOp</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""
</span><span style="color:#4e9a06"> Initialize error handler with subsystem ID and acknowledge callback.
</span><span style="color:#4e9a06"> """</span>
<span style="color:#8f5902;font-style:italic"># Set subsystem ID (helpful to identify which subsystem the error belongs to)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set_subsystem_id</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">1</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Set callback for error acknowledgment</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set_acknowledge_callback</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">on_error_acknowledged</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Error handler configured"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">on_error_acknowledged</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""
</span><span style="color:#4e9a06"> Callback when user acknowledges an error.
</span><span style="color:#4e9a06"> Reset the input parameter to clear the error condition.
</span><span style="color:#4e9a06"> """</span>
<span style="color:#000">result</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#4e9a06">f</span><span style="color:#4e9a06">"</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_service_parameter_path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">/input"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#0000cf;font-weight:bold">0</span>
<span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">result</span> <span style="color:#204a87;font-weight:bold">is</span> <span style="color:#204a87;font-weight:bold">not</span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#204a87;font-weight:bold">and</span> <span style="color:#000">result</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">status</span> <span style="color:#ce5c00;font-weight:bold">!=</span> <span style="color:#000">motorcortex</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">OK</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">error</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Failed to reset input parameter after error acknowledgment."</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">else</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Error acknowledged - input parameter reset to 0"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">iterate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""
</span><span style="color:#4e9a06"> Monitor input parameter and trigger errors based on value ranges.
</span><span style="color:#4e9a06"> Uses rising edge detection to avoid repeated error triggers.
</span><span style="color:#4e9a06"> """</span>
<span style="color:#000">result</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">getParameter</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#4e9a06">f</span><span style="color:#4e9a06">"</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_service_parameter_path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">/input"</span>
<span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">result</span> <span style="color:#204a87;font-weight:bold">is</span> <span style="color:#204a87;font-weight:bold">not</span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#204a87;font-weight:bold">and</span> <span style="color:#000">result</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">status</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#000">motorcortex</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">OK</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">result</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span>
<span style="color:#8f5902;font-style:italic"># Rising edge detection - only trigger when value changes</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">__last_value</span> <span style="color:#ce5c00;font-weight:bold">!=</span> <span style="color:#000">value</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#8f5902;font-style:italic"># Value ranges trigger different error levels</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#0000cf;font-weight:bold">10</span> <span style="color:#ce5c00;font-weight:bold"><</span> <span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold"><</span> <span style="color:#0000cf;font-weight:bold">20</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Triggering WARNING level error."</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_warning</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">1001</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#0000cf;font-weight:bold">20</span> <span style="color:#ce5c00;font-weight:bold"><=</span> <span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold"><</span> <span style="color:#0000cf;font-weight:bold">30</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Triggering FORCED_DISENGAGE level error."</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_forced_disengage</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">2001</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#0000cf;font-weight:bold">30</span> <span style="color:#ce5c00;font-weight:bold"><=</span> <span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold"><</span> <span style="color:#0000cf;font-weight:bold">40</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Triggering SHUTDOWN level error."</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_shutdown</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">3001</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#0000cf;font-weight:bold">40</span> <span style="color:#ce5c00;font-weight:bold"><=</span> <span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold"><</span> <span style="color:#0000cf;font-weight:bold">50</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Triggering EMERGENCY_STOP level error."</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">trigger_emergency_stop</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">error_code</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">4001</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">__last_value</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">value</span>
<span style="color:#8f5902;font-style:italic"># Use self.wait() to keep watchdog alive</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">0.5</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">onExit</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""
</span><span style="color:#4e9a06"> Cleanup on exit.
</span><span style="color:#4e9a06"> """</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Error handler app exiting"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">__name__</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#4e9a06">"__main__"</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">config</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">McxClientAppConfiguration</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">name</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#4e9a06">"ErrorExample"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">config</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set_config_paths</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">deployed_config</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#4e9a06">"/etc/motorcortex/config/services/services_config.json"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">non_deployed_config</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#4e9a06">"services_config.json"</span>
<span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">config</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">load_config</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#000">app</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">ErrorHandlerApp</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">config</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">app</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">run</span><span style="color:#000;font-weight:bold">()</span>
</code></pre></div><p><strong>services_config.json for Error Handler Example:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-json" data-lang="json"><span style="color:#000;font-weight:bold">{</span>
<span style="color:#204a87;font-weight:bold">"Services"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000;font-weight:bold">[</span>
<span style="color:#000;font-weight:bold">{</span>
<span style="color:#204a87;font-weight:bold">"Name"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">"ErrorExample"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#204a87;font-weight:bold">"Enabled"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87;font-weight:bold">true</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#204a87;font-weight:bold">"Config"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000;font-weight:bold">{</span>
<span style="color:#204a87;font-weight:bold">"login"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">"admin"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#204a87;font-weight:bold">"password"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">"password"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#204a87;font-weight:bold">"target_url"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">"wss://192.168.1.100"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#204a87;font-weight:bold">"autoStart"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87;font-weight:bold">true</span>
<span style="color:#000;font-weight:bold">},</span>
<span style="color:#204a87;font-weight:bold">"Parameters"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000;font-weight:bold">{</span>
<span style="color:#204a87;font-weight:bold">"Version"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">"1.0"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#204a87;font-weight:bold">"Children"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000;font-weight:bold">[</span>
<span style="color:#000;font-weight:bold">{</span>
<span style="color:#204a87;font-weight:bold">"Name"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">"userParameters"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#204a87;font-weight:bold">"Children"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000;font-weight:bold">[</span>
<span style="color:#000;font-weight:bold">{</span>
<span style="color:#204a87;font-weight:bold">"Name"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">"input"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#204a87;font-weight:bold">"Type"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">"int, input"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#204a87;font-weight:bold">"Value"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#0000cf;font-weight:bold">0</span>
<span style="color:#000;font-weight:bold">}</span>
<span style="color:#000;font-weight:bold">]</span>
<span style="color:#000;font-weight:bold">}</span>
<span style="color:#000;font-weight:bold">]</span>
<span style="color:#000;font-weight:bold">},</span>
<span style="color:#204a87;font-weight:bold">"Watchdog"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000;font-weight:bold">{</span>
<span style="color:#204a87;font-weight:bold">"Enabled"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87;font-weight:bold">true</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#204a87;font-weight:bold">"Disabled"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87;font-weight:bold">false</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#204a87;font-weight:bold">"high"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#0000cf;font-weight:bold">1000000</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#204a87;font-weight:bold">"tooHigh"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#0000cf;font-weight:bold">5000000</span>
<span style="color:#000;font-weight:bold">}</span>
<span style="color:#000;font-weight:bold">}</span>
<span style="color:#000;font-weight:bold">]</span>
<span style="color:#000;font-weight:bold">}</span>
</code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">logging</span>
<span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">time</span>
<span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">json</span>
<span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">pathlib</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">Path</span>
<span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">src.mcx_client_app</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">McxClientApp</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">McxClientAppConfiguration</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">ThreadSafeValue</span>
<span style="color:#204a87;font-weight:bold">class</span> <span style="color:#000">DataLoggerConfiguration</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">McxClientAppConfiguration</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#4e9a06">"""Configuration for data logger with custom parameters."""</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">log_interval</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">1.0</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">log_file</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"data_log.json"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">parameters_to_log</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">list</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#ce5c00;font-weight:bold">**</span><span style="color:#000">kwargs</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">log_interval</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">log_interval</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">log_file</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">log_file</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">parameters_to_log</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">parameters_to_log</span> <span style="color:#204a87;font-weight:bold">or</span> <span style="color:#000;font-weight:bold">[]</span>
<span style="color:#204a87">super</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#ce5c00;font-weight:bold">**</span><span style="color:#000">kwargs</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">class</span> <span style="color:#000">DataLogger</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">McxClientApp</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#4e9a06">"""Logs specified parameters to file at regular intervals."""</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">options</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000">DataLoggerConfiguration</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87">super</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">options</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000">DataLoggerConfiguration</span> <span style="color:#8f5902;font-style:italic"># Type hint for IDE</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">logged_data</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000;font-weight:bold">[]</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">latest_values</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">ThreadSafeValue</span><span style="color:#000;font-weight:bold">({})</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">data_subscription</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">startOp</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Subscribe to parameters to log."""</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#204a87;font-weight:bold">not</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">parameters_to_log</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">warning</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"No parameters configured for logging"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">return</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">data_subscription</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sub</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">subscribe</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">parameters_to_log</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">group_alias</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#4e9a06">"data_logger"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">frq_divider</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">10</span>
<span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">result</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">data_subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">result</span> <span style="color:#204a87;font-weight:bold">and</span> <span style="color:#000">result</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">status</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#000">motorcortex</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">OK</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">data_subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">notify</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_onDataUpdate</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Logging </span><span style="color:#4e9a06">{</span><span style="color:#204a87">len</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">parameters_to_log</span><span style="color:#000;font-weight:bold">)</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06"> parameters"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">_onDataUpdate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">msg</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Update latest values from subscription."""</span>
<span style="color:#000">data</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000;font-weight:bold">{}</span>
<span style="color:#204a87;font-weight:bold">for</span> <span style="color:#000">i</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">param_path</span> <span style="color:#204a87;font-weight:bold">in</span> <span style="color:#204a87">enumerate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">parameters_to_log</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#000">data</span><span style="color:#000;font-weight:bold">[</span><span style="color:#000">param_path</span><span style="color:#000;font-weight:bold">]</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">msg</span><span style="color:#000;font-weight:bold">[</span><span style="color:#000">i</span><span style="color:#000;font-weight:bold">]</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">latest_values</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">data</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">iterate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Log current values to file."""</span>
<span style="color:#000">values</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">latest_values</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">values</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">log_entry</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000;font-weight:bold">{</span>
<span style="color:#4e9a06">"timestamp"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000">time</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">time</span><span style="color:#000;font-weight:bold">(),</span>
<span style="color:#4e9a06">"data"</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000">values</span>
<span style="color:#000;font-weight:bold">}</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">logged_data</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">append</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">log_entry</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">debug</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Logged: </span><span style="color:#4e9a06">{</span><span style="color:#000">log_entry</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">log_interval</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">onExit</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Save logged data to file."""</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">data_subscription</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">data_subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">unsubscribe</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_saveLogFile</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Data logger stopped. </span><span style="color:#4e9a06">{</span><span style="color:#204a87">len</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">logged_data</span><span style="color:#000;font-weight:bold">)</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06"> entries saved."</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">_saveLogFile</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Write logged data to JSON file."""</span>
<span style="color:#204a87;font-weight:bold">try</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#204a87;font-weight:bold">with</span> <span style="color:#204a87">open</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">log_file</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#4e9a06">'w'</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#204a87;font-weight:bold">as</span> <span style="color:#000">f</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">json</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">dump</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">logged_data</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">f</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">indent</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">2</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Log saved to </span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">log_file</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">except</span> <span style="color:#c00;font-weight:bold">Exception</span> <span style="color:#204a87;font-weight:bold">as</span> <span style="color:#000">e</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">error</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Failed to save log: </span><span style="color:#4e9a06">{</span><span style="color:#000">e</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">__name__</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#4e9a06">"__main__"</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">config</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">DataLoggerConfiguration</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">target_url</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#4e9a06">"wss://192.168.1.100"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">log_interval</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">0.5</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">log_file</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#4e9a06">"sensor_data.json"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">parameters_to_log</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#000;font-weight:bold">[</span>
<span style="color:#4e9a06">"root/Sensors/Temperature"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#4e9a06">"root/Sensors/Pressure"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#4e9a06">"root/Sensors/Humidity"</span>
<span style="color:#000;font-weight:bold">]</span>
<span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Update the config paths below to match your deployment requirements</span>
<span style="color:#8f5902;font-style:italic"># deployed_config: Path used when DEPLOYED env var is set (on production systems)</span>
<span style="color:#8f5902;font-style:italic"># non_deployed_config: Path used during local development</span>
<span style="color:#000">config</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set_config_paths</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">deployed_config</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#4e9a06">"/etc/motorcortex/config/services/data_logger.json"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">non_deployed_config</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#4e9a06">"config.json"</span>
<span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">app</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">DataLogger</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">config</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">app</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">run</span><span style="color:#000;font-weight:bold">()</span>
</code></pre></div><hr>
<h2 id="api-reference">API Reference</h2>
<p>This section provides comprehensive documentation for the key APIs used in Motorcortex client applications.</p>
<h3 id="robot-control-api">Robot Control API</h3>
<h4 id="motionprogram">MotionProgram</h4>
<p><strong>Purpose:</strong> Create and send motion programs to the robot manipulator.</p>
<p><strong>Import:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">robot_control.motion_program</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">MotionProgram</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">Waypoint</span>
</code></pre></div><p><strong>Key Classes:</strong></p>
<p><strong><code>Waypoint</code></strong> - Represents a waypoint in the motion path:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#000">Waypoint</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">pose</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">list</span><span style="color:#000;font-weight:bold">[</span><span style="color:#204a87">float</span><span style="color:#000;font-weight:bold">],</span> <span style="color:#8f5902;font-style:italic"># Cartesian [x, y, z, rx, ry, rz] or joint angles</span>
<span style="color:#000">smoothing_factor</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">0.1</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#8f5902;font-style:italic"># Waypoint smoothing [0..1]</span>
<span style="color:#000">next_segment_velocity_factor</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">1.0</span> <span style="color:#8f5902;font-style:italic"># Segment velocity factor [0..1]</span>
<span style="color:#000;font-weight:bold">)</span>
</code></pre></div><p><strong><code>MotionProgram</code></strong> - Build and send motion programs:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#8f5902;font-style:italic"># Initialize</span>
<span style="color:#000">mp</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">MotionProgram</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">motorcortex_types</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Add Linear Motion</span>
<span style="color:#000">mp</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">addMoveL</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">waypoint_list</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">list</span><span style="color:#000;font-weight:bold">[</span><span style="color:#000">Waypoint</span><span style="color:#000;font-weight:bold">],</span>
<span style="color:#000">velocity</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">0.1</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#8f5902;font-style:italic"># m/s</span>
<span style="color:#000">acceleration</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">0.2</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#8f5902;font-style:italic"># m/s²</span>
<span style="color:#000">rotational_velocity</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">3.18</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#8f5902;font-style:italic"># rad/s</span>
<span style="color:#000">rotational_acceleration</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">6.37</span> <span style="color:#8f5902;font-style:italic"># rad/s²</span>
<span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Add Joint Motion</span>
<span style="color:#000">mp</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">addMoveJ</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">waypoint_list</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">list</span><span style="color:#000;font-weight:bold">[</span><span style="color:#000">Waypoint</span><span style="color:#000;font-weight:bold">],</span>
<span style="color:#000">rotational_velocity</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">3.18</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#8f5902;font-style:italic"># rad/s</span>
<span style="color:#000">rotational_acceleration</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">6.37</span> <span style="color:#8f5902;font-style:italic"># rad/s²</span>
<span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Add Circular Motion</span>
<span style="color:#000">mp</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">addMoveC</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">waypoint_list</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">list</span><span style="color:#000;font-weight:bold">[</span><span style="color:#000">Waypoint</span><span style="color:#000;font-weight:bold">],</span>
<span style="color:#000">angle</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#8f5902;font-style:italic"># Rotation angle in rad</span>
<span style="color:#000">velocity</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">0.1</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#8f5902;font-style:italic"># m/s</span>
<span style="color:#000">acceleration</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">0.2</span> <span style="color:#8f5902;font-style:italic"># m/s²</span>
<span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Add Wait Command</span>
<span style="color:#000">mp</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">addWait</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">timeout_s</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#8f5902;font-style:italic"># Wait duration in seconds</span>
<span style="color:#000">path</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#8f5902;font-style:italic"># Optional parameter to wait for</span>
<span style="color:#000">value</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">1</span> <span style="color:#8f5902;font-style:italic"># Value to compare</span>
<span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Add Set Parameter Command</span>
<span style="color:#000">mp</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">addSet</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">path</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#8f5902;font-style:italic"># Parameter path</span>
<span style="color:#000">value</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">|</span> <span style="color:#204a87">int</span> <span style="color:#ce5c00;font-weight:bold">|</span> <span style="color:#204a87">bool</span> <span style="color:#8f5902;font-style:italic"># Value to set</span>
<span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Send Program</span>
<span style="color:#000">mp</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">send</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">program_name</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"Undefined"</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#000">motorcortex</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">ParameterTree</span>
</code></pre></div><p><strong>Example:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">math</span>
<span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">robot_control.motion_program</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">MotionProgram</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">Waypoint</span>
<span style="color:#8f5902;font-style:italic"># Create waypoints</span>
<span style="color:#000">home</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">Waypoint</span><span style="color:#000;font-weight:bold">([</span><span style="color:#0000cf;font-weight:bold">0.4</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">0.0</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">0.35</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">math</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">pi</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">])</span>
<span style="color:#000">target</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">Waypoint</span><span style="color:#000;font-weight:bold">([</span><span style="color:#0000cf;font-weight:bold">0.5</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">0.2</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">0.1</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">math</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">pi</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">])</span>
<span style="color:#8f5902;font-style:italic"># Build motion program</span>
<span style="color:#000">mp</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">MotionProgram</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">motorcortex_types</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">mp</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">addMoveL</span><span style="color:#000;font-weight:bold">([</span><span style="color:#000">home</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">target</span><span style="color:#000;font-weight:bold">],</span> <span style="color:#000">velocity</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">0.3</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">acceleration</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">0.5</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">mp</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">send</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"my_program"</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
</code></pre></div><h4 id="robotcommand">RobotCommand</h4>
<p><strong>Purpose:</strong> Control robot state machine (engage, play, stop, etc.)</p>
<p><strong>Import:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">robot_control.robot_command</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">RobotCommand</span>
</code></pre></div><p><strong>Initialize:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#000">robot</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">RobotCommand</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">motorcortex_types</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">system_id</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">)</span>
</code></pre></div><p><strong>State Control Methods:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#8f5902;font-style:italic"># State transitions</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">off</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span> <span style="color:#8f5902;font-style:italic"># Switch to Off state</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">disengage</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span> <span style="color:#8f5902;font-style:italic"># Switch to Disengage state</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">engage</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span> <span style="color:#8f5902;font-style:italic"># Switch to Engage state</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">acknowledge</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">timeout_s</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">20.0</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span> <span style="color:#8f5902;font-style:italic"># Acknowledge errors</span>
<span style="color:#8f5902;font-style:italic"># Mode control</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">manualCartMode</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span> <span style="color:#8f5902;font-style:italic"># Manual Cartesian motion</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">manualJointMode</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span> <span style="color:#8f5902;font-style:italic"># Manual joint motion</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">semiAutoMode</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span> <span style="color:#8f5902;font-style:italic"># Semi-auto mode</span>
<span style="color:#8f5902;font-style:italic"># Motion control</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">moveToPoint</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">target_joint_coord_rad</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">list</span><span style="color:#000;font-weight:bold">[</span><span style="color:#204a87">float</span><span style="color:#000;font-weight:bold">],</span>
<span style="color:#000">v_max</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">0.5</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#8f5902;font-style:italic"># rad/s</span>
<span style="color:#000">a_max</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">1.0</span> <span style="color:#8f5902;font-style:italic"># rad/s²</span>
<span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">moveToStart</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">timeout_s</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span>
<span style="color:#8f5902;font-style:italic"># Program control</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">play</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">wait_time</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">1.0</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#000">InterpreterStates</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">pause</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">wait_time</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">1.0</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#000">InterpreterStates</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">stop</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">wait_time</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">1.0</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#000">InterpreterStates</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">reset</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">wait_time</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">1.0</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#000">InterpreterStates</span>
<span style="color:#8f5902;font-style:italic"># State query</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">getState</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#000">InterpreterStates</span>
</code></pre></div><p><strong>Example:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">robot_control.robot_command</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">RobotCommand</span>
<span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">robot_control.system_defs</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">InterpreterStates</span>
<span style="color:#000">robot</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">RobotCommand</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">motorcortex_types</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Engage robot</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">engage</span><span style="color:#000;font-weight:bold">():</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Robot engaged"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Play program</span>
<span style="color:#000">state</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">play</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">state</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#000">InterpreterStates</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">MOTION_NOT_ALLOWED_S</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">moveToStart</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">10</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">play</span><span style="color:#000;font-weight:bold">()</span>
</code></pre></div><h4 id="system-definitions">System Definitions</h4>
<p><strong>Purpose:</strong> Enums for robot states, modes, and interpreter states.</p>
<p><strong>Import:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">robot_control.system_defs</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">States</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">StateEvents</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">Modes</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">ModeCommands</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">InterpreterStates</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">InterpreterEvents</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">MotionGeneratorStates</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">FrameTypes</span>
<span style="color:#000;font-weight:bold">)</span>
</code></pre></div><p><strong>Key Enums:</strong></p>
<p><strong><code>States</code></strong> - Robot state machine states:</p>
<ul>
<li><code>OFF_S</code> (1), <code>DISENGAGED_S</code> (2), <code>ENGAGED_S</code> (4), <code>ESTOP_OFF_S</code> (7)</li>
</ul>
<p><strong><code>InterpreterStates</code></strong> - Motion program interpreter states:</p>
<ul>
<li><code>PROGRAM_STOP_S</code> (0) - Program stopped</li>
<li><code>PROGRAM_RUN_S</code> (1) - Program running</li>
<li><code>PROGRAM_PAUSE_S</code> (2) - Program paused</li>
<li><code>MOTION_NOT_ALLOWED_S</code> (3) - Motion not allowed</li>
<li><code>PROGRAM_IS_DONE</code> (200) - Program completed</li>
</ul>
<p><strong><code>Modes</code></strong> - Robot operation modes:</p>
<ul>
<li><code>PAUSE_M</code> (1), <code>AUTO_RUN_M</code> (2), <code>MANUAL_JOINT_MODE_M</code> (3), <code>MANUAL_CART_MODE_M</code> (4)</li>
</ul>
<p><strong>Example:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">robot_control.system_defs</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">InterpreterStates</span>
<span style="color:#000">state</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">getState</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">state</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#000">InterpreterStates</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">PROGRAM_STOP_S</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Program is stopped"</span><span style="color:#000;font-weight:bold">)</span>
</code></pre></div><h3 id="motorcortex-python-api">Motorcortex Python API</h3>
<h4 id="request">Request</h4>
<p><strong>Purpose:</strong> Send requests to Motorcortex server (get/set parameters).</p>
<p><strong>Key Methods:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#8f5902;font-style:italic"># Get parameter</span>
<span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">getParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">path</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#000">Reply</span>
<span style="color:#8f5902;font-style:italic"># Returns: Reply with .value attribute</span>
<span style="color:#8f5902;font-style:italic"># Set parameter</span>
<span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">path</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">value</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000">Any</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">type_name</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span>
<span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#000">Reply</span>
<span style="color:#8f5902;font-style:italic"># Set multiple parameters</span>
<span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameterList</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">param_list</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">list</span><span style="color:#000;font-weight:bold">[</span><span style="color:#204a87">dict</span><span style="color:#000;font-weight:bold">])</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#000">Reply</span>
<span style="color:#8f5902;font-style:italic"># param_list format: [{"path": "...", "value": ...}, ...]</span>
<span style="color:#8f5902;font-style:italic"># Create subscription group</span>
<span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">createGroup</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">path_list</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">list</span><span style="color:#000;font-weight:bold">[</span><span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">],</span>
<span style="color:#000">group_alias</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">frq_divider</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">int</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">1</span>
<span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#000">Reply</span>
</code></pre></div><p><strong>Example:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#8f5902;font-style:italic"># Get parameter</span>
<span style="color:#000">result</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">getParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"root/Sensors/Temperature"</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">result</span> <span style="color:#204a87;font-weight:bold">and</span> <span style="color:#000">result</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">status</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#000">motorcortex</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">OK</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">temp</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">result</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span>
<span style="color:#8f5902;font-style:italic"># Set parameter</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"root/Control/Speed"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">0.5</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
</code></pre></div><h4 id="subscription">Subscription</h4>
<p><strong>Purpose:</strong> Subscribe to real-time parameter updates.</p>
<p><strong>Key Methods:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#8f5902;font-style:italic"># Subscribe to parameters</span>
<span style="color:#000">sub</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">subscribe</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">path_list</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">list</span><span style="color:#000;font-weight:bold">[</span><span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">]</span> <span style="color:#ce5c00;font-weight:bold">|</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">group_alias</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">frq_divider</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">int</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">1</span>
<span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#000">Subscription</span>
<span style="color:#8f5902;font-style:italic"># Subscription object methods</span>
<span style="color:#000">subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">timeout_sec</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">1.0</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#000">StatusMsg</span> <span style="color:#8f5902;font-style:italic"># Wait for subscription</span>
<span style="color:#000">subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">notify</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">callback</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000">Callable</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#8f5902;font-style:italic"># Register observer</span>
<span style="color:#000">subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">read</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">list</span><span style="color:#000;font-weight:bold">[</span><span style="color:#000">Parameter</span><span style="color:#000;font-weight:bold">]</span> <span style="color:#8f5902;font-style:italic"># Read latest values</span>
<span style="color:#000">subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">layout</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">list</span><span style="color:#000;font-weight:bold">[</span><span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">]</span> <span style="color:#8f5902;font-style:italic"># Get parameter paths</span>
<span style="color:#000">subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">unsubscribe</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#8f5902;font-style:italic"># Unsubscribe</span>
</code></pre></div><p><strong>Example:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#8f5902;font-style:italic"># Subscribe to parameters</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sensor_sub</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sub</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">subscribe</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000;font-weight:bold">[</span><span style="color:#4e9a06">"root/Sensors/Temperature"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#4e9a06">"root/Sensors/Pressure"</span><span style="color:#000;font-weight:bold">],</span>
<span style="color:#000">group_alias</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#4e9a06">"sensors"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">frq_divider</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">100</span>
<span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">result</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sensor_sub</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">result</span> <span style="color:#204a87;font-weight:bold">and</span> <span style="color:#000">result</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">status</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#000">motorcortex</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">OK</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sensor_sub</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">notify</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_onSensorUpdate</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">_onSensorUpdate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">msg</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#000">temp</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">msg</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span>
<span style="color:#000">pressure</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">msg</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">1</span><span style="color:#000;font-weight:bold">]</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span>
<span style="color:#8f5902;font-style:italic"># Process values...</span>
</code></pre></div><h4 id="parametertree">ParameterTree</h4>
<p><strong>Purpose:</strong> Represents the parameter tree structure from the server.</p>
<p><strong>Key Methods:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#8f5902;font-style:italic"># Load parameter tree</span>
<span style="color:#000">parameter_tree</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">load</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">parameter_tree_msg</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Get parameter info</span>
<span style="color:#000">parameter_tree</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">getInfo</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">parameter_path</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#000">ParameterInfo</span>
<span style="color:#000">parameter_tree</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">getDataType</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">parameter_path</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#000">DataType</span>
<span style="color:#000">parameter_tree</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">getParameterTree</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">list</span><span style="color:#000;font-weight:bold">[</span><span style="color:#000">ParameterInfo</span><span style="color:#000;font-weight:bold">]</span>
</code></pre></div><h3 id="mcxclientapp-framework">McxClientApp Framework</h3>
<h4 id="mcxclientapp">McxClientApp</h4>
<p><strong>Base class for main-thread execution.</strong></p>
<p><strong>Inherited Attributes:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span> <span style="color:#8f5902;font-style:italic"># motorcortex.Request</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sub</span> <span style="color:#8f5902;font-style:italic"># motorcortex.Subscription</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">parameter_tree</span> <span style="color:#8f5902;font-style:italic"># motorcortex.ParameterTree</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">motorcortex_types</span> <span style="color:#8f5902;font-style:italic"># motorcortex.MessageTypes</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span> <span style="color:#8f5902;font-style:italic"># McxClientAppConfiguration</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">running</span> <span style="color:#8f5902;font-style:italic"># ThreadSafeValue[bool]</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">watchdog</span> <span style="color:#8f5902;font-style:italic"># McxWatchdog</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">errorHandler</span> <span style="color:#8f5902;font-style:italic"># McxErrorHandler</span>
</code></pre></div><p><strong>Methods to Override:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">startOp</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Called after connection, before iterate starts."""</span>
<span style="color:#204a87;font-weight:bold">pass</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">iterate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Main application logic (called repeatedly)."""</span>
<span style="color:#204a87;font-weight:bold">pass</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">onExit</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Cleanup before disconnect."""</span>
<span style="color:#204a87;font-weight:bold">pass</span>
</code></pre></div><p><strong>Inherited Methods:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">timeout</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">30</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">testinterval</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">0.2</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait_for</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">param</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">value</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">object</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">timeout</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">30</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">operat</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"=="</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">reset</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#8f5902;font-style:italic"># Set running to False</span>
</code></pre></div><h4 id="mcxclientappconfiguration">McxClientAppConfiguration</h4>
<p><strong>Configuration class for client applications.</strong></p>
<p><strong>Complete Class Definition:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">logging</span>
<span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">os</span>
<span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">json</span>
<span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">.state_def</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">State</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">load_config_json</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">path</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">name</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">dict</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""
</span><span style="color:#4e9a06"> Load and validate configuration JSON from `path`.
</span><span style="color:#4e9a06">
</span><span style="color:#4e9a06"> Args:
</span><span style="color:#4e9a06"> path (str): Path to the configuration JSON file.
</span><span style="color:#4e9a06"> name (str): Name of the service to extract configuration for.
</span><span style="color:#4e9a06">
</span><span style="color:#4e9a06"> Returns:
</span><span style="color:#4e9a06"> dict: Configuration dictionary for the specified service.
</span><span style="color:#4e9a06"> """</span>
<span style="color:#204a87;font-weight:bold">assert</span> <span style="color:#000">path</span> <span style="color:#204a87;font-weight:bold">is</span> <span style="color:#204a87;font-weight:bold">not</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#4e9a06">"Configuration path must be provided"</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#204a87;font-weight:bold">not</span> <span style="color:#000">os</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">path</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">exists</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">path</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87;font-weight:bold">raise</span> <span style="color:#c00;font-weight:bold">AssertionError</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"[ERROR] Configuration file not found: </span><span style="color:#4e9a06">{</span><span style="color:#000">path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">with</span> <span style="color:#204a87">open</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">path</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#4e9a06">'r'</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#204a87;font-weight:bold">as</span> <span style="color:#000">f</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">data</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">json</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">load</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">f</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">services_data</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">data</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Services"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000;font-weight:bold">[])</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">services_data</span> <span style="color:#204a87;font-weight:bold">is</span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#204a87;font-weight:bold">or</span> <span style="color:#204a87">type</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">services_data</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#204a87;font-weight:bold">is</span> <span style="color:#204a87;font-weight:bold">not</span> <span style="color:#204a87">list</span> <span style="color:#204a87;font-weight:bold">or</span> <span style="color:#204a87">len</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">services_data</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#204a87;font-weight:bold">raise</span> <span style="color:#c00;font-weight:bold">ValueError</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"[ERROR] No service data found in deployed configuration file: </span><span style="color:#4e9a06">{</span><span style="color:#000">path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">matched</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span>
<span style="color:#204a87;font-weight:bold">for</span> <span style="color:#000">service</span> <span style="color:#204a87;font-weight:bold">in</span> <span style="color:#000">services_data</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">service</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Name"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#4e9a06">""</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#000">name</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">matched</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">service</span>
<span style="color:#204a87;font-weight:bold">break</span>
<span style="color:#204a87;font-weight:bold">else</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#204a87;font-weight:bold">raise</span> <span style="color:#c00;font-weight:bold">ValueError</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"[ERROR] No service with name '</span><span style="color:#4e9a06">{</span><span style="color:#000">name</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">' found in configuration file: </span><span style="color:#4e9a06">{</span><span style="color:#000">path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">config_data</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">matched</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Config"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000;font-weight:bold">{})</span> <span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">matched</span> <span style="color:#204a87;font-weight:bold">is</span> <span style="color:#204a87;font-weight:bold">not</span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#204a87;font-weight:bold">else</span> <span style="color:#000;font-weight:bold">{}</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#204a87;font-weight:bold">not</span> <span style="color:#204a87">isinstance</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">config_data</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#204a87">dict</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87;font-weight:bold">raise</span> <span style="color:#c00;font-weight:bold">ValueError</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"[ERROR] Invalid configuration format in </span><span style="color:#4e9a06">{</span><span style="color:#000">path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">; expected object/dict."</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">return</span> <span style="color:#000">config_data</span>
<span style="color:#204a87;font-weight:bold">class</span> <span style="color:#000">McxClientAppConfiguration</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""
</span><span style="color:#4e9a06"> Configuration options for McxClientApp.
</span><span style="color:#4e9a06">
</span><span style="color:#4e9a06"> Attributes:
</span><span style="color:#4e9a06"> name (str): Name of the client application.
</span><span style="color:#4e9a06"> login (str): Username for authenticating with the Motorcortex server.
</span><span style="color:#4e9a06"> password (str): Password for authenticating with the Motorcortex server.
</span><span style="color:#4e9a06"> target_url (str): Local Development WebSocket URL (e.g., 'wss://localhost').
</span><span style="color:#4e9a06"> target_url_deployed (str): Deployed WebSocket URL (default: 'wss://localhost').
</span><span style="color:#4e9a06"> cert (str): Local Development path to SSL certificate (e.g., 'mcx.cert.crt').
</span><span style="color:#4e9a06"> cert_deployed (str): Deployed path to SSL certificate (default: '/etc/ssl/certs/mcx.cert.pem').
</span><span style="color:#4e9a06"> statecmd_param (str): Parameter path for state commands (default: 'root/Logic/stateCommand').
</span><span style="color:#4e9a06"> state_param (str): Parameter path for current state (default: 'root/Logic/state').
</span><span style="color:#4e9a06"> run_during_states (list[State]|None): List of allowed states for iterate() (default None).
</span><span style="color:#4e9a06"> autoStart (bool): Start automatically upon connection (default: True).
</span><span style="color:#4e9a06"> start_button_path (str|None): Custom start button path (default: None).
</span><span style="color:#4e9a06"> enable_watchdog (bool): Enable watchdog functionality (default: True).
</span><span style="color:#4e9a06"> enable_error_handler (bool): Enable error handler functionality (default: True).
</span><span style="color:#4e9a06"> error_reset_param (str): Error reset parameter path (default: 'root/Services/:fromState/resetErrors').
</span><span style="color:#4e9a06">
</span><span style="color:#4e9a06"> Note:
</span><span style="color:#4e9a06"> When inheriting, call super().__init__(**kwargs) AFTER setting custom attributes.
</span><span style="color:#4e9a06"> """</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">name</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">login</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">|</span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">password</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">|</span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">target_url</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"wss://localhost"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">target_url_deployed</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"wss://localhost"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">cert</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"mcx.cert.crt"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">cert_deployed</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"/etc/ssl/certs/mcx.cert.pem"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">statecmd_param</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">|</span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"root/Logic/stateCommand"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">state_param</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">|</span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"root/Logic/state"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">run_during_states</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">list</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">autoStart</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">bool</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">True</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">start_button_path</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">|</span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">enable_watchdog</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">bool</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">True</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">enable_error_handler</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">bool</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">True</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">error_reset_param</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"root/Services/:fromState/resetErrors"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#ce5c00;font-weight:bold">**</span><span style="color:#000">kwargs</span>
<span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">name</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">name</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">login</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">login</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">password</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">password</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">target_url</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">target_url</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">target_url_deployed</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">target_url_deployed</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">cert</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">cert</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">cert_deployed</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">cert_deployed</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">statecmd_param</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">statecmd_param</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">state_param</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">state_param</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_run_during_states</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">State</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">list_from</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">run_during_states</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">autoStart</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">autoStart</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">start_button_path</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">start_button_path</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">enable_watchdog</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">enable_watchdog</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">enable_error_handler</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">enable_error_handler</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">error_reset_param</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">error_reset_param</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">deployed_config</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"/etc/motorcortex/config/services/services_config.json"</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">non_deployed_config</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">|</span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span>
<span style="color:#204a87;font-weight:bold">for</span> <span style="color:#000">key</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">value</span> <span style="color:#204a87;font-weight:bold">in</span> <span style="color:#000">kwargs</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">items</span><span style="color:#000;font-weight:bold">():</span>
<span style="color:#204a87">setattr</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">key</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">value</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">__has_config</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">False</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">load_config</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Load configuration from the set config paths."""</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">is_deployed</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">config_file</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">deployed_config</span>
<span style="color:#204a87;font-weight:bold">else</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">config_file</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">non_deployed_config</span>
<span style="color:#000">config_data</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">load_config_json</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">config_file</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">name</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">name</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">for</span> <span style="color:#000">key</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">value</span> <span style="color:#204a87;font-weight:bold">in</span> <span style="color:#000">config_data</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">items</span><span style="color:#000;font-weight:bold">():</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">key</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#4e9a06">"run_during_states"</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_run_during_states</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">State</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">list_from</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#204a87">hasattr</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">key</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87">setattr</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">key</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">value</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">__has_config</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">True</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Configuration loaded from </span><span style="color:#4e9a06">{</span><span style="color:#4e9a06">'deployed'</span> <span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">is_deployed</span> <span style="color:#204a87;font-weight:bold">else</span> <span style="color:#4e9a06">'non-deployed'</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06"> config file: </span><span style="color:#4e9a06">{</span><span style="color:#000">config_file</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">set_config_paths</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">deployed_config</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">|</span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">non_deployed_config</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">|</span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Set the configuration file paths for deployed and non-deployed environments."""</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">deployed_config</span> <span style="color:#204a87;font-weight:bold">is</span> <span style="color:#204a87;font-weight:bold">not</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">deployed_config</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">deployed_config</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">non_deployed_config</span> <span style="color:#204a87;font-weight:bold">is</span> <span style="color:#204a87;font-weight:bold">not</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">non_deployed_config</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">non_deployed_config</span>
<span style="color:#5c35cc;font-weight:bold">@property</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">is_deployed</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Check if running in deployed environment (checks DEPLOYED env var)."""</span>
<span style="color:#204a87;font-weight:bold">return</span> <span style="color:#000">os</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">environ</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"DEPLOYED"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#204a87;font-weight:bold">False</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#204a87;font-weight:bold">is</span> <span style="color:#204a87;font-weight:bold">not</span> <span style="color:#204a87;font-weight:bold">False</span>
<span style="color:#5c35cc;font-weight:bold">@property</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">certificate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Get certificate path based on deployment status."""</span>
<span style="color:#204a87;font-weight:bold">return</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">cert_deployed</span> <span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">is_deployed</span> <span style="color:#204a87;font-weight:bold">else</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">cert</span>
<span style="color:#5c35cc;font-weight:bold">@property</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">ip_address</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Get target URL based on deployment status."""</span>
<span style="color:#204a87;font-weight:bold">return</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">target_url_deployed</span> <span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">is_deployed</span> <span style="color:#204a87;font-weight:bold">else</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">target_url</span>
<span style="color:#5c35cc;font-weight:bold">@property</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">get_parameter_path</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Get parameter path root for the service."""</span>
<span style="color:#204a87;font-weight:bold">return</span> <span style="color:#4e9a06">f</span><span style="color:#4e9a06">"root/Services/</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">name</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span>
<span style="color:#5c35cc;font-weight:bold">@property</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">get_service_parameter_path</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Get service parameter path root."""</span>
<span style="color:#204a87;font-weight:bold">return</span> <span style="color:#4e9a06">f</span><span style="color:#4e9a06">"root/Services/</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">name</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">/serviceParameters"</span>
<span style="color:#5c35cc;font-weight:bold">@property</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">get_start_button_parameter_path</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Get parameter path for start button control."""</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">start_button_path</span> <span style="color:#204a87;font-weight:bold">is</span> <span style="color:#204a87;font-weight:bold">not</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#4e9a06">"root/"</span> <span style="color:#204a87;font-weight:bold">in</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">start_button_path</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#204a87;font-weight:bold">return</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">start_button_path</span>
<span style="color:#204a87;font-weight:bold">else</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#204a87;font-weight:bold">return</span> <span style="color:#4e9a06">f</span><span style="color:#4e9a06">"</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_parameter_path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">/</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">start_button_path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span>
<span style="color:#204a87;font-weight:bold">return</span> <span style="color:#4e9a06">f</span><span style="color:#4e9a06">"</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_parameter_path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">/enableService"</span>
</code></pre></div><p><strong>Key Properties:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_parameter_path</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">str</span> <span style="color:#8f5902;font-style:italic"># "root/Services/{ServiceName}"</span>
<span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_service_parameter_path</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">str</span> <span style="color:#8f5902;font-style:italic"># "root/Services/{ServiceName}/serviceParameters"</span>
<span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">is_deployed</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span> <span style="color:#8f5902;font-style:italic"># True if DEPLOYED env var set</span>
<span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">certificate</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">str</span> <span style="color:#8f5902;font-style:italic"># Cert path based on deployment</span>
<span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">ip_address</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">str</span> <span style="color:#8f5902;font-style:italic"># URL based on deployment</span>
</code></pre></div><p><strong>Key Methods:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#000">config</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set_config_paths</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000">deployed_config</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">non_deployed_config</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span>
<span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">config</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">load_config</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#8f5902;font-style:italic"># Load from JSON</span>
</code></pre></div><h4 id="using-commandword-for-external-commands">Using commandWord for External Commands</h4>
<p><strong>CRITICAL: commandWord is a built-in service parameter</strong> that allows external systems (DESK tool, other services, web interfaces) to send commands to your service.</p>
<p><strong>Path:</strong> <code>root/Services/{ServiceName}/commandWord</code></p>
<p><strong>Best Practices:</strong></p>
<p>✅ <strong>DO:</strong></p>
<ul>
<li>Use ChangeDetector with <code>trigger_on_zero=False</code> to monitor commandWord</li>
<li>Define command values clearly (1=reset, 2=pause, 3=resume, etc.)</li>
<li>Acknowledge commands by resetting commandWord to 0 after processing</li>
<li>Document command values in your class/file docstring</li>
<li>Check for changes in <code>iterate()</code>, not in subscription callback</li>
</ul>
<p>❌ <strong>DON’T:</strong></p>
<ul>
<li>Create custom button parameters when commandWord exists</li>
<li>Process commands in subscription callback (too fast, wrong thread)</li>
<li>Forget to reset commandWord to 0 after processing (prevents re-triggering)</li>
<li>Use <code>trigger_on_zero=True</code> for commands (will trigger on acknowledgment)</li>
</ul>
<p><strong>Example Pattern:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">class</span> <span style="color:#000">MyService</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">McxClientApp</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#4e9a06">"""
</span><span style="color:#4e9a06"> My Service Application.
</span><span style="color:#4e9a06">
</span><span style="color:#4e9a06"> Command Values (via commandWord):
</span><span style="color:#4e9a06"> - 1: Reset counter
</span><span style="color:#4e9a06"> - 2: Pause operation
</span><span style="color:#4e9a06"> - 3: Resume operation
</span><span style="color:#4e9a06"> - 4: Double speed
</span><span style="color:#4e9a06"> """</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">options</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87">super</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">options</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">command_detector</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">ChangeDetector</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">startOp</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#8f5902;font-style:italic"># Subscribe to commandWord</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">cmd_sub</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sub</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">subscribe</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000;font-weight:bold">[</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_parameter_path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">/commandWord"</span><span style="color:#000;font-weight:bold">],</span>
<span style="color:#4e9a06">"cmd"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">frq_divider</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">10</span>
<span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">cmd_sub</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">notify</span><span style="color:#000;font-weight:bold">(</span><span style="color:#204a87;font-weight:bold">lambda</span> <span style="color:#000">msg</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">command_detector</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set_value</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">msg</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]))</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">iterate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#8f5902;font-style:italic"># Check for commands (ignore changes TO zero)</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">command_detector</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">has_changed</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">trigger_on_zero</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#204a87;font-weight:bold">False</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#000">cmd</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">command_detector</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_value</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_process_command</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">cmd</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Acknowledge command</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#4e9a06">f</span><span style="color:#4e9a06">"</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_parameter_path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">/commandWord"</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#0000cf;font-weight:bold">0</span>
<span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#8f5902;font-style:italic"># Your main logic here</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">1.0</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">_process_command</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">cmd</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">int</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#4e9a06">"""Process commandWord values."""</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">cmd</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#0000cf;font-weight:bold">1</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">counter</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">0</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#000">cmd</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#0000cf;font-weight:bold">2</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">paused</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">True</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#000">cmd</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#0000cf;font-weight:bold">3</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">paused</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">False</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#000">cmd</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#0000cf;font-weight:bold">4</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">speed</span> <span style="color:#ce5c00;font-weight:bold">*=</span> <span style="color:#0000cf;font-weight:bold">2</span>
</code></pre></div><p><strong>Why This Pattern Works:</strong></p>
<ol>
<li><strong>External control</strong> - DESK tool or other systems can send commands by setting commandWord</li>
<li><strong>No custom parameters</strong> - Uses built-in commandWord, no need to define button parameters</li>
<li><strong>Change detection</strong> - Only processes when value actually changes (not every subscription update)</li>
<li><strong>Acknowledgment</strong> - Resetting to 0 allows same command to be sent again</li>
<li><strong>Main thread processing</strong> - Commands handled in iterate(), not subscription callback</li>
</ol>
<h4 id="changedetector">ChangeDetector</h4>
<p><strong>Thread-safe value change detector for monitoring parameter changes.</strong></p>
<p><strong>Purpose:</strong> Detect when a parameter value changes and check for changes in your iterate() loop. Perfect for command words, state changes, or any parameter requiring change detection. Unlike callbacks, this gives you full control over when to check and process changes.</p>
<p><strong>Import:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">src.mcx_client_app.ChangeDetector</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">ChangeDetector</span>
</code></pre></div><p><strong>Constructor:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#000">ChangeDetector</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#8f5902;font-style:italic"># No parameters needed</span>
</code></pre></div><p><strong>Key Methods:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#000">detector</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set_value</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#8f5902;font-style:italic"># Call after updating internal value to check for changes</span>
<span style="color:#000">detector</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_value</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#000">Any</span> <span style="color:#8f5902;font-style:italic"># Get current value</span>
<span style="color:#000">detector</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">has_changed</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">keep</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">bool</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">False</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span> <span style="color:#8f5902;font-style:italic"># Check if value changed (clears flag unless keep=True)</span>
<span style="color:#000">detector</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">reset</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span> <span style="color:#8f5902;font-style:italic"># Reset internal state</span>
</code></pre></div><p><strong>Complete Usage Example:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">src.mcx_client_app</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">McxClientApp</span>
<span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">src.mcx_client_app.ChangeDetector</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">ChangeDetector</span>
<span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">motorcortex</span>
<span style="color:#204a87;font-weight:bold">class</span> <span style="color:#000">MyApp</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">McxClientApp</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">options</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87">super</span><span style="color:#000;font-weight:bold">()</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">options</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Create detector for commandWord parameter</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">command_detector</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">ChangeDetector</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">command_subscription</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#204a87;font-weight:bold">None</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">startOp</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#8f5902;font-style:italic"># Subscribe to the parameter</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">command_subscription</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">sub</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">subscribe</span><span style="color:#000;font-weight:bold">(</span>
<span style="color:#000;font-weight:bold">[</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"</span><span style="color:#4e9a06">{</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">options</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_parameter_path</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">/commandWord"</span><span style="color:#000;font-weight:bold">],</span>
<span style="color:#4e9a06">"command_group"</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">frq_divider</span><span style="color:#ce5c00;font-weight:bold">=</span><span style="color:#0000cf;font-weight:bold">10</span>
<span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">result</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">command_subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">result</span> <span style="color:#204a87;font-weight:bold">and</span> <span style="color:#000">result</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">status</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#000">motorcortex</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">OK</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">command_subscription</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">notify</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_on_command_update</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">_on_command_update</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">msg</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#4e9a06">"""Callback for commandWord updates (runs in subscription thread)."""</span>
<span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">msg</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">[</span><span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">]</span>
<span style="color:#8f5902;font-style:italic"># Update detector value (fast operation in subscription thread)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">command_detector</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">_ChangeDetector__value</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">value</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">command_detector</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set_value</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">iterate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#8f5902;font-style:italic"># Check if commandWord changed and process in main thread</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">command_detector</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">has_changed</span><span style="color:#000;font-weight:bold">():</span>
<span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">command_detector</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get_value</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Command received: </span><span style="color:#4e9a06">{</span><span style="color:#000">value</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#0000cf;font-weight:bold">1</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Reset command received"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">reset</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#0000cf;font-weight:bold">2</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Start command received"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">start</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold">==</span> <span style="color:#0000cf;font-weight:bold">3</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Stop command received"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">stop</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">0.1</span><span style="color:#000;font-weight:bold">)</span>
</code></pre></div><p><strong>Use Cases:</strong></p>
<ul>
<li><strong>Command words</strong>: Multiple command values (1=reset, 2=start, 3=stop)</li>
<li><strong>State monitoring</strong>: Detect when states change</li>
<li><strong>Configuration changes</strong>: React when settings are updated</li>
<li><strong>Multi-value inputs</strong>: Switches with multiple positions</li>
</ul>
<p><strong>Important Notes:</strong></p>
<ul>
<li>✅ <strong>Check changes in iterate()</strong> - Use <code>has_changed()</code> to detect changes in main thread</li>
<li>✅ <strong>Full control</strong> - You decide when to check and process changes</li>
<li>✅ <strong>Thread-safe</strong> - Uses <code>ThreadSafeValue</code> internally for cross-thread communication</li>
<li>✅ <strong>Simple pattern</strong> - Subscription updates value, iterate() checks for changes</li>
<li>⚠️ <strong>Subscription callback is fast</strong> - Only updates internal value, no processing
<strong>Thread-safe container for sharing data between threads.</strong></li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">src.mcx_client_app</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">ThreadSafeValue</span>
<span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">ThreadSafeValue</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">initial_value</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#000">value</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">set</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">new_value</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#8f5902;font-style:italic"># Thread-safe write</span>
<span style="color:#000">current</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#000">value</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span> <span style="color:#8f5902;font-style:italic"># Thread-safe read</span>
</code></pre></div><h3 id="client-app-examples">Client App Examples</h3>
<p>Reference implementations in <a href="examples/">examples/</a> directory:</p>
<ul>
<li><a href="examples/start_button.py">examples/start_button.py</a> - Start/stop button control</li>
<li><a href="examples/robot_app.py">examples/robot_app.py</a> - Robot motion application</li>
<li><a href="examples/custom_button.py">examples/custom_button.py</a> - Custom button handling with subscriptions</li>
<li><a href="examples/error_app.py">examples/error_app.py</a> - Error handler demonstration</li>
<li><a href="examples/datalogger.py">examples/datalogger.py</a> - Data logging application</li>
</ul>
<hr>
<h2 id="coding-conventions">Coding Conventions</h2>
<h3 id="naming-conventions">Naming Conventions</h3>
<ul>
<li><strong>Methods</strong>: <code>camelCase</code> (e.g., <code>moveToPoint</code>, <code>addMoveL</code>, <code>getParameter</code>)</li>
<li><strong>Classes</strong>: <code>PascalCase</code> (e.g., <code>RobotCommand</code>, <code>MotionProgram</code>, <code>McxClientApp</code>)</li>
<li><strong>Variables</strong>: <code>snake_case</code> (e.g., <code>motorcortex_types</code>, <code>waypoint_list</code>, <code>current_temperature</code>)</li>
<li><strong>Private methods</strong>: Prefix with <code>_</code> (e.g., <code>_setupSubscriptions</code>, <code>_onButtonPress</code>)</li>
<li><strong>Constants/Enums</strong>: <code>UPPER_CASE</code> (e.g., <code>State.ENGAGED_S</code>, <code>MAX_RETRIES</code>)</li>
</ul>
<h3 id="type-hints">Type Hints</h3>
<p>Always use type hints for function parameters and return values:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">processData</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">values</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">list</span><span style="color:#000;font-weight:bold">[</span><span style="color:#204a87">float</span><span style="color:#000;font-weight:bold">],</span> <span style="color:#000">threshold</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">10.0</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Process sensor values against threshold."""</span>
<span style="color:#204a87;font-weight:bold">pass</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">_onUpdate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">msg</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""Callback receives motorcortex message (no type hint needed)."""</span>
<span style="color:#204a87;font-weight:bold">pass</span>
</code></pre></div><p>Use modern Python 3.9+ syntax:</p>
<ul>
<li><code>list[str]</code> instead of <code>List[str]</code></li>
<li><code>dict[str, int]</code> instead of <code>Dict[str, int]</code></li>
<li><code>str | None</code> instead of <code>Optional[str]</code></li>
</ul>
<h3 id="docstrings">Docstrings</h3>
<p>Every public class and method must have a docstring:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">wait_for</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">param</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">value</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">object</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">timeout</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">float</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#0000cf;font-weight:bold">30</span><span style="color:#000;font-weight:bold">,</span>
<span style="color:#000">operat</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#204a87">str</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#4e9a06">"=="</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-></span> <span style="color:#204a87">bool</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#4e9a06">"""
</span><span style="color:#4e9a06"> Wait for a parameter to meet a condition.
</span><span style="color:#4e9a06">
</span><span style="color:#4e9a06"> Args:
</span><span style="color:#4e9a06"> param (str): Parameter path to monitor.
</span><span style="color:#4e9a06"> value (object): Value to compare against.
</span><span style="color:#4e9a06"> timeout (float): Timeout in seconds. Default 30.
</span><span style="color:#4e9a06"> operat (str): Comparison operator ("==", "!=", "<", "<=", ">", ">=", "in").
</span><span style="color:#4e9a06">
</span><span style="color:#4e9a06"> Returns:
</span><span style="color:#4e9a06"> bool: True if condition met, False if timeout.
</span><span style="color:#4e9a06">
</span><span style="color:#4e9a06"> Raises:
</span><span style="color:#4e9a06"> StopSignal: If stop signal received before condition met.
</span><span style="color:#4e9a06"> """</span>
</code></pre></div><h3 id="error-handling">Error Handling</h3>
<ol>
<li><strong>Check return values</strong> - Many methods return <code>bool</code> for success/failure:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">if</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">robot</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">engage</span><span style="color:#000;font-weight:bold">():</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Robot engaged"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">else</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">error</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Failed to engage robot"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">return</span>
</code></pre></div><ol start="2">
<li><strong>Use specific exceptions</strong>:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">if</span> <span style="color:#204a87;font-weight:bold">not</span> <span style="color:#204a87">isinstance</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">speed</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#204a87">float</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87;font-weight:bold">raise</span> <span style="color:#c00;font-weight:bold">TypeError</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Speed must be float, got </span><span style="color:#4e9a06">{</span><span style="color:#204a87">type</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">speed</span><span style="color:#000;font-weight:bold">)</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">if</span> <span style="color:#000">speed</span> <span style="color:#ce5c00;font-weight:bold"><</span> <span style="color:#0000cf;font-weight:bold">0</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#204a87;font-weight:bold">raise</span> <span style="color:#c00;font-weight:bold">ValueError</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Speed must be positive, got </span><span style="color:#4e9a06">{</span><span style="color:#000">speed</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
</code></pre></div><ol start="3">
<li><strong>Handle StopSignal</strong> gracefully:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">try</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">wait</span><span style="color:#000;font-weight:bold">(</span><span style="color:#0000cf;font-weight:bold">10</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#204a87;font-weight:bold">except</span> <span style="color:#000">StopSignal</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">info</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">"Operation stopped by user"</span><span style="color:#000;font-weight:bold">)</span>
<span style="color:#8f5902;font-style:italic"># Cleanup here</span>
</code></pre></div><ol start="4">
<li><strong>Log errors with context</strong>:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">try</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">result</span> <span style="color:#ce5c00;font-weight:bold">=</span> <span style="color:#3465a4">self</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">req</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">setParameter</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">param</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">value</span><span style="color:#000;font-weight:bold">)</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">get</span><span style="color:#000;font-weight:bold">()</span>
<span style="color:#204a87;font-weight:bold">except</span> <span style="color:#c00;font-weight:bold">Exception</span> <span style="color:#204a87;font-weight:bold">as</span> <span style="color:#000">e</span><span style="color:#000;font-weight:bold">:</span>
<span style="color:#000">logging</span><span style="color:#ce5c00;font-weight:bold">.</span><span style="color:#000">error</span><span style="color:#000;font-weight:bold">(</span><span style="color:#4e9a06">f</span><span style="color:#4e9a06">"Failed to set </span><span style="color:#4e9a06">{</span><span style="color:#000">param</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06"> to </span><span style="color:#4e9a06">{</span><span style="color:#000">value</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">: </span><span style="color:#4e9a06">{</span><span style="color:#000">e</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">"</span><span style="color:#000;font-weight:bold">)</span>
</code></pre></div><h3 id="code-structure">Code Structure</h3>
<ol>
<li><strong>Imports at top</strong> - Group by standard library, third-party, local:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#8f5902;font-style:italic"># Standard library</span>
<span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">logging</span>
<span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">time</span>
<span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">pathlib</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">Path</span>
<span style="color:#8f5902;font-style:italic"># Third-party</span>
<span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">motorcortex</span>
<span style="color:#8f5902;font-style:italic"># Local</span>
<span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">src.mcx_client_app</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">McxClientApp</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">McxClientAppConfiguration</span>
<span style="color:#204a87;font-weight:bold">from</span> <span style="color:#000">robot_control.robot_command</span> <span style="color:#204a87;font-weight:bold">import</span> <span style="color:#000">RobotCommand</span>
</code></pre></div><ol start="2">
<li><strong>Class organization</strong>:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python"><span style="color:#204a87;font-weight:bold">class</span> <span style="color:#000">MyApp</span><span style="color:#000;font-weight:bold">(</span><span style="color:#000">McxClientApp</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#4e9a06">"""Docstring."""</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">__init__</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">options</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#4e9a06">"""Initialize."""</span>
<span style="color:#204a87;font-weight:bold">pass</span>
<span style="color:#8f5902;font-style:italic"># Lifecycle methods</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">startOp</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87;font-weight:bold">pass</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">iterate</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87;font-weight:bold">pass</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">onExit</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87;font-weight:bold">pass</span>
<span style="color:#8f5902;font-style:italic"># Private helper methods (alphabetical)</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">_doSomething</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87;font-weight:bold">pass</span>
<span style="color:#204a87;font-weight:bold">def</span> <span style="color:#000">_onCallback</span><span style="color:#000;font-weight:bold">(</span><span style="color:#3465a4">self</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#000">msg</span><span style="color:#000;font-weight:bold">):</span>
<span style="color:#204a87;font-weight:bold">pass</span>
</code></pre></div><ol start="3">
<li><strong>Keep methods focused</strong> - One responsibility per method, max ~30 lines</li>
</ol>
<h3 id="best-practices-summary">Best Practices Summary</h3>
<p>✅ <strong>DO:</strong></p>
<ul>
<li>Use descriptive variable names (<code>current_temperature</code> not <code>temp</code>)</li>
<li>Extract logic into private helper methods</li>
<li>Use subscriptions for real-time data</li>
<li>Check return values and handle errors</li>
<li>Log important events at INFO level</li>
<li>Log detailed data at DEBUG level</li>
<li>Use <code>self.wait()</code> instead of <code>time.sleep()</code></li>
<li>Unsubscribe in <code>onExit()</code></li>
<li>Use <code>ThreadSafeValue</code> for shared data between threads</li>
</ul>
<p>❌ <strong>DON’T:</strong></p>
<ul>
<li>Poll parameters in <code>iterate()</code> - use subscriptions</li>
<li>Block in subscription callbacks</li>
<li>Access <code>self.req</code> or <code>self.sub</code> before connection</li>
<li>Initialize in <code>iterate()</code> - use <code>__init__()</code> or <code>startOp()</code></li>
<li>Use <code>time.sleep()</code> - use <code>self.wait()</code></li>
<li>Create new subscriptions in <code>iterate()</code></li>
<li>Ignore return values from robot commands</li>
<li>Leave subscriptions active after exit</li>
</ul>
<hr>
<h2 id="code-generation-servicesdelines-for-ai-agents">Code Generation servicesdelines for AI Agents</h2>
<p>When asked to create an mcx-client-app:</p>
<ol>
<li>
<p><strong>Ask clarifying questions</strong> if the user’s intent is unclear:</p>
<ul>
<li>What should the app do? (monitor, control, automate, log)</li>
<li>What parameters to interact with?</li>
<li>Should it use start/stop button or state-based execution?</li>
<li>Any custom configuration needed?</li>
</ul>
</li>
<li>
<p><strong>Start with the simplest solution</strong>:</p>
<ul>
<li>Use <code>McxClientApp</code> unless threading is explicitly needed</li>
<li>Use built-in <code>McxClientAppConfiguration</code> unless custom parameters needed</li>
<li>Keep <code>iterate()</code> focused and delegate to helper methods</li>
</ul>
</li>
<li>
<p><strong>Follow the structure</strong>:</p>
<ul>
<li>Custom configuration class (if needed)</li>
<li>Main application class inheriting from <code>McxClientApp</code> or <code>McxClientAppThread</code></li>
<li>Lifecycle methods: <code>__init__</code>, <code>startOp</code>, <code>iterate</code>, <code>onExit</code></li>
<li>Private helper methods for logic</li>
<li>Subscription callbacks</li>
<li>Main block with configuration and <code>app.run()</code></li>
</ul>
</li>
<li>
<p><strong>Include comprehensive docstrings</strong> - Other AI agents will read your code</p>
</li>
<li>
<p><strong>Add logging</strong> at key points for debugging</p>
</li>
<li>
<p><strong>Handle errors gracefully</strong> with try/except and logging</p>
</li>
<li>
<p><strong>Show config.json example</strong> with required parameters</p>
</li>
<li>
<p><strong>Explain what the app does</strong> in comments</p>
</li>
</ol>
<hr>
<p><strong>Remember:</strong> The goal is to create clean, maintainable, production-ready code that other developers (and AI agents) can understand and modify easily. Always double-check for mistakes and avoid duplicating functions or classes that already exist.</p>
Example
The following prompt was given to Copilot, running Claude Sonnot 4.5 model in agent mode in VS Code:
Prompt
Create a client app that subscribes to a new parameter called input and then does an equation with that file liek y=ax^2+bx+c then output to another new parameter. Make sure this goes as fast as possible.
It subsequently edited the mcx-client-app.py file to to the following:
Click to show `mcx-client-app.py`
#
# Developer : Coen Smeets (Coen@vectioneer.com)
# All rights reserved. Copyright (c) 2025 VECTIONEER.
#
"""
MCX-Client-App: Quadratic Equation Processor
REQUIRED PARAMETERS:
Add this to the end of the parameters.json file in the config/user folder:
{
"Name": "services",
"Children": [
{
"Name": "QuadraticProcessor",
"Children": [
{
"Name": "input",
"Type": "double, input",
"Value": 0.0
},
{
"Name": "output",
"Type": "double, parameter_volatile",
"Value": 0.0
}
]
}
]
}
SETUP: 1) Add JSON above to parameters.json, 2) Restart Motorcortex, 3) Verify in DESK tool
"""
import sys
from pathlib import Path
import logging
logging.basicConfig(level=logging.INFO)
# Add parent directory to path to import mcx_client_app
sys.path.insert(0, str(Path(__file__).parent.parent))
from src.mcx_client_app import McxClientApp, McxClientAppConfiguration, ThreadSafeValue
import motorcortex
class QuadraticProcessorConfiguration(McxClientAppConfiguration):
"""
Configuration for Quadratic Equation Processor with equation coefficients.
Attributes:
a (float): Coefficient for x^2 term (default: 1.0)
b (float): Coefficient for x term (default: 0.0)
c (float): Constant term (default: 0.0)
input_param (str): Path to input parameter
output_param (str): Path to output parameter
"""
def __init__(
self,
a: float = 1.0,
b: float = 0.0,
c: float = 0.0,
input_param: str = "root/UserParameters/services/QuadraticProcessor/input",
output_param: str = "root/UserParameters/services/QuadraticProcessor/output",
**kwargs
):
self.a = a
self.b = b
self.c = c
self.input_param = input_param
self.output_param = output_param
super().__init__(**kwargs)
class QuadraticProcessorApp(McxClientApp):
"""
High-speed quadratic equation processor: y = ax^2 + bx + c
Subscribes to input parameter and outputs result instantly.
"""
def __init__(self, options: QuadraticProcessorConfiguration):
super().__init__(options)
self.options: QuadraticProcessorConfiguration # Type hint for IDE
# Thread-safe storage for input value
self.current_input = ThreadSafeValue(0.0)
self.input_subscription = None
logging.info(f"Quadratic processor initialized: y = {self.options.a}x^2 + {self.options.b}x + {self.options.c}")
def startOp(self) -> None:
"""
Initialize output parameter and subscribe to input parameter for real-time updates.
"""
# Initialize output parameter to zero
try:
self.req.setParameter(self.options.output_param, 0.0).get()
logging.info(f"Output parameter initialized: {self.options.output_param}")
except Exception as e:
logging.error(f"Failed to initialize output parameter: {e}")
raise
# Subscribe to input parameter with high frequency for speed
self.input_subscription = self.sub.subscribe(
[self.options.input_param],
group_alias="quadratic_input",
frq_divider=1 # Maximum speed - update every cycle
)
result = self.input_subscription.get()
if result and result.status == motorcortex.OK:
self.input_subscription.notify(self._onInputUpdate)
logging.info(f"Subscribed to input parameter: {self.options.input_param}")
else:
logging.error(f"Failed to subscribe to input parameter: {self.options.input_param}")
self.reset() # Stop the application
def iterate(self) -> None:
"""
Calculate quadratic equation and output result.
Runs continuously for maximum throughput.
"""
# Get current input value (thread-safe)
x = self.current_input.get()
# Calculate: y = ax^2 + bx + c
y = self.options.a * (x ** 2) + self.options.b * x + self.options.c
# Write output (parameter_volatile type optimized for frequent writes)
try:
self.req.setParameter(self.options.output_param, y).get()
except Exception as e:
logging.error(f"Failed to set output parameter: {e}")
# Minimal wait for responsiveness - allows stop signal checking
self.wait(0.001) # 1ms for maximum speed while still responsive
def onExit(self) -> None:
"""
Clean up on exit - unsubscribe from input parameter.
"""
if self.input_subscription:
try:
self.input_subscription.unsubscribe()
logging.info("Unsubscribed from input parameter")
except Exception as e:
logging.error(f"Error unsubscribing: {e}")
logging.info("Quadratic processor stopped")
if __name__ == "__main__":
client_options = QuadraticProcessorConfiguration(
a=1.0, # Coefficient for x^2
b=0.0, # Coefficient for x
c=0.0 # Constant term
)
client_options.set_config_paths(
deployed_config="/etc/motorcortex/config/services/quadratic_processor.json",
non_deployed_config="config.json"
)
app = QuadraticProcessorApp(client_options)
app.run()
As can be seen, Copilot was able to create a complete client application that does exactly what was requested in the prompt, including description on how to set up the required parameters in the parameters.json file and comments throughout the code explaining each part of the implementation.