Client app Tools
109 minute read
These tools help you build a Motorcortex-python client application. They are built on top of motorcortex-python and simplify common tasks such as setting up a client from scratch, adding new parameters like buttons, and deploying to the Motorcortex application.
Read the tabs in order. The Client App Template tab covers the installation and use of a ready-to-use client application template that you can take as a starting point. The tabs after it introduce further tools that build on that template — though using the template is not a requirement for the principles they show.
Download the client application template
A client application template is provided that sets up a basic Motorcortex-python client application, as a starting point for your own client applications. It includes the connection setup, parameter tree initialisation, and a structure for running your own functions.
Tip
The Motorcortex VS Code extension can scaffold this template for you: click the + button in the Developer tools section of the Motorcortex view and choose Python MCX Client App, or run Motorcortex: Create Python MCX Client App from the Command Palette. It sets up the folder and the Python environment, and fills in part of the configuration.
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 out of the repository 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. Edit this file to add your own functionality and logic. The next tab walks through mcx-client-app.py and shows 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(name="mcx-client-app")
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()
The name argument is required, and it must match the Name of your service in services_config.json — that is how load_config() finds the right entry. Without the load_config() call the configuration file is never read, and the application falls back to its defaults (wss://localhost).
When you run this code, it creates a basic Motorcortex-python client application that connects to your specified Motorcortex server. Since no custom logic is added yet, the application simply connects and repeatedly prints “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 automatically disengages the robot and disconnects 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, use the usual self.sub.subscribe. Create subscriptions in the startOp method, which runs once after the connection is established, so that they are set up before the first iterate call.
You can also use the notify method on the subscription to define a callback that runs whenever the subscribed parameter changes — but it runs in a different thread, so be cautious when accessing shared data.
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 interrupts 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(name="mcx-client-app")
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 = 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/services_config.json"
],
}
Motorcortex loads and applies 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().
Info
Backward compatibility: MCX-Robot versions prior to 3.14.0 do not load services into the parameter tree. Client applications still function without the watchdog and error handler features, but service parameters must be added manually 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 deployed, load_config reads the deployed configuration file; otherwise it reads the non-deployed one. Behind the scenes it checks for an environment variable called DEPLOYED, which the generated systemd unit sets automatically — see the Deploying tab for how that works.
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(
name="ExampleMcxClientApp",
login="admin",
password="vectioneer",
target_url="wss://192.168.2.100"
)
app = ExampleMcxClientApp(client_options)
app.run()
Note
target_url and cert are not used when deployed, as the deployment environment sets these automatically. To change the deployed values, use target_url_deployed and cert_deployed instead. This means you can use the same json file for both local development and deployment.
Information on deployed_config (important)
The default path for deployed_config is set up so all service configurations are stored in the services folder, which you can deploy from the
Motorcortex portal. This keeps all service configurations in one place and gives you a convenient way of deploying them. If you do not have a services folder, 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 does 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 behaviour (default:None). When it isNone, the application usesroot/Services/{name}/enableService. A value that does not start withroot/is treated as a name relative toroot/Services/{name}/. - 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.
Customising 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 short example creates a custom configuration class, uses it with McxClientApp, deploys it, and then changes the configuration through the portal.
The custom parameter is called print_integer, and the client application prints it from its iterate() method.
-
Create a custom configuration class by inheriting from
McxClientAppConfiguration, adding theprint_integerparameter.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) -
Change the
iteratemethod 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
services_config.jsonwith the following content (do not forget to set your login credentials andtarget_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, point it at the configuration files, load them, and pass it to the
ExampleMcxClientAppconstructor. Thenamemust match theNameused in step 3.if __name__ == "__main__": client_options = CustomMcxClientAppConfiguration(name="print-integer-client-app") 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()When you run this application, it prints the value of
print_integerfrom the configuration file —42in this case — every second. Without theload_config()call it would print the class default of10instead.INFO:root:Waiting for start signal... INFO:root:Running user iterate... INFO:root:Number: 42 INFO:root:Number: 42 INFO:root:Number: 42 INFO:root:Number: 42 ^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
.conffolder. Create a new folder calledservicesif it does not exist yet. Then change, or create, theservices_config.jsonto include this entry in theServiceslist:{ "Name": "print-integer-client-app", "Enabled": true, "Parameters": { "Version": "1.0", "Children": [] }, "Config": { "login": "admin", "password": "vectioneer", "target_url": "wss://192.168.2.100", "print_integer": 42 }, "Watchdog": { "Enabled": true, "Disabled": true, "high": 1000000, "tooHigh": 5000000 } }Then deploy the
.conffolder. The deployed configuration file is used instead of the local file when the application is deployed, so the same code picks up the portal-managedprint_integervalue. -
Create a Debian package for deployment, see the Deploying tab for more information, 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 set up a client app to start and stop its iterate method using a button in the Motorcortex GUI. By default the McxClientApp class starts automatically, but you can disable that and hook a button up to the enableService parameter instead.
-
In your
services_config.jsonfile, set theautoStartparameter tofalsein the service’sConfigsection:{ "Version": "1.0.0", "Services": [ { "Name": "mcx-client-app", "Enabled": true, "Config": { "autoStart": false } } ] } -
Create a switch in the Motorcortex GUI that is linked to the
root/Services/mcx-client-app/enableServiceboolean parameter —root/Services/{name}/enableService, where{name}is thenameyou passed toMcxClientAppConfiguration. When this switch is toggled on, theiteratemethod starts executing. When toggled off, it stops.
Info
Backward compatibility: on MCX-Robot versions where enableService is not present, set the path to the start button in services_config.json with the start_button_path configuration key.
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/{name}/commandWord and allows external systems or GUI buttons to send commands to your service.
Note
This example uses the basic Motorcortex Anthropomorphic Robot application, which you can download from the Motorcortex Store. Make sure it is running and that you can connect to it with Motorcortex Desk. Other Motorcortex applications 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 (for example, 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 from Motorcortex 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 as an entry in theServiceslist:
{
"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.
"""
# Status codes live on the protobuf namespace, not on the motorcortex module
motorcortex_msg = self.motorcortex_types.motorcortex()
# 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_msg.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_updateis 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/{name}/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 use 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 raising a warning or an emergency stop.
Watchdog configuration
Info
Backward compatibility: MCX-Robot versions prior to 3.14.0 do not support the watchdog. On those, 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:
{
"Version": "1.0.0",
"Services": [
{
"Name": "mcx-client-app",
"Enabled": true,
"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. The Watchdog section contains the settings for the watchdog behaviour 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 logs a warning.tooHigh: The threshold (in microseconds) for a missed heartbeat. If the heartbeat does not arrive within this value, Motorcortex assumes the client application has crashed and takes 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.
Info
Backward compatibility: on MCX-Robot versions prior to 3.14.0, a client app cannot register errors and warnings.
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 the 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(
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(
code=self.ERROR_BATTERY_CRITICAL
)
elif battery_voltage < self.LOW_THRESHOLD:
self.errorHandler.trigger_warning(
code=self.ERROR_BATTERY_LOW
)
Example
1. Configure the 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(code=1002)
elif battery_voltage < 11.0:
# Low battery warning
self.errorHandler.trigger_warning(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 separate PC that connects to the Motorcortex server over the network, you can deploy the client application directly to the Motorcortex application. This is useful for standalone setups, or when you want the client application to always run alongside Motorcortex.
Tip
The Motorcortex VS Code extension guides you through the deployment process, and is the recommended way to do it.
A Docker container creates and packages 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 use a Python script to create the .deb on your local machine without Docker, but additional Python dependencies may break on the Motorcortex application when deployed.
After creating a .deb file, deploy it to the controller through the portal, or manually over SSH and then install it with 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 are relative to the root of your client application project, from where you 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. Incontainermode, the package runs inside a container on the target system. Invenvmode, the package creates a virtual environment on the target system, which is backwards compatible with older MCX images. (Default:venv).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;nullskips wheel building entirely).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 falls 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. To customise the service file, provide your own template by specifying the SERVICE_TEMPLATE option in the package_config.json file.
The custom service template follows the systemd service file format, see Understanding systemd units and unit files.
makeDeb.py substitutes these placeholders when it builds the package. Any of them that your template omits is simply not filled in:
| Placeholder | Replaced with |
|---|---|
@PACKAGE_NAME@ |
PACKAGE_NAME from the config |
@VERSION@ |
VERSION from the config |
@DESCRIPTION@ |
DESCRIPTION from the config |
@PYTHON_SCRIPT@ |
PYTHON_SCRIPT from the config |
@ENVIRONMENT@ |
Environment=DEPLOYED=True |
@EXEC_START_PRE@ |
the container image checks in container mode; empty in venv mode |
@EXEC_START@ |
the podman run command, or the virtual-environment Python invocation |
@EXEC_STOP@ |
ExecStop=…podman stop… in container mode; empty in venv mode |
Warning
A custom template must set DEPLOYED — either through @ENVIRONMENT@ or as a literal Environment=DEPLOYED=True line. Without it, is_deployed stays false on the controller, the application reads the local configuration path instead of the deployed one, and your deployed credentials and target_url are ignored. In container mode a template without @EXEC_START_PRE@ and @EXEC_STOP@ also loses the image check and the container shutdown.
The template that ships in the deploying directory is below, for reference:
[Unit]
Description=@DESCRIPTION@
After=ethercat.target motorcortex.service
PartOf=motorcortex.service
StartLimitIntervalSec=0
[Service]
Type=simple
ExecStartPre=/bin/sleep 3
Environment=DEPLOYED=TRUE
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 it from the 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. Use the Docker method described in the Docker tab instead. 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 your
package_config.jsonfile, because the default path points inside the build container. Point it at the template that ships in the template’sdeployingdirectory:"SERVICE_TEMPLATE": "deploying/template.service.in" -
Run the build script Execute the
makeDeb.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 is named differently.
The template contains instructions for Copilot to use, so it can help you code faster with AI. It knows how the client app works and how it interacts with the Motorcortex controller.
For the best results, be concrete in your prompts and provide as much context as possible. If you need a parameter, name it directly and say whether it is an input or an output from the client app’s perspective. Build an app in steps, starting with a basic version and adding features in subsequent prompts, rather than asking for everything at once.
Warning
Copilot and similar AI tools can speed up development, but they can also introduce errors or insecure code. Always review and test the generated code thoroughly before deploying it in a production environment.
Copilot automatically looks for instructions in the .github folder that the template adds. This folder contains a file called copilot-instructions.md with detailed instructions on how to create Motorcortex client applications. Even with those instructions in place, providing context in the prompt itself still gives the best results.
Click to show `copilot-instructions.md`
Note
Hyperlinks may not work here. You can view the file directly in the template repository at git.vectioneer.com/pub/mcx-client-app-template.
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 the Claude Sonnet 4.5 model in agent mode in VS Code:
Info
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 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 == self.motorcortex_types.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(
name="quadratic-processor",
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/services_config.json",
non_deployed_config="services_config.json"
)
client_options.load_config()
app = QuadraticProcessorApp(client_options)
app.run()
Copilot created a complete client application that does what the prompt asked, including a description of how to set up the required parameters in parameters.json and comments explaining each part of the implementation.
Note
Generated code still needs review. In this case the entry point needed the required name argument, the configuration paths corrected to services_config.json, and a load_config() call added before it would run — exactly the kind of check the warning above is about.