Client app Tools

This chapter will show how to setup a client app.

Client App Tools

This tutorial will show you some helpful tools to build a Motorcortex python client application. These tools are built on top of motorcortex-python and simplify common tasks such as setting up a client from scratch, adding new parameters like buttons, deploying to the Motorcortex application.

The first Client App Template tab will guide you through the installation and usage of a ready-to-use client application template that can be used as a starting point for your own client applications. The subsequent tabs will introduce additional tools and utilities that can enhance your client application development experience and will build upon the provided template. However, using the template is not a requirement to use the other principles shown in the other tabs.


Download of the Client Application Template

For the ease of use, a client application template is provided that sets up a basic Motorcortex-python client application. This template can be used as a starting point for your own client applications. It includes the necessary connection setup, parameter tree initialization, and provides a structure for running functions.

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

The file mcx-client-app.py is the main entry point for the client application. You can edit this file to add your own functionality and logic. The next tab will guide you through the mcx-client-app.py file and show how to extend it for your own use cases.


mcx-client-app.py

To create everything you want, the main file to edit is mcx-client-app.py. This file contains the main logic of the client application and is where you can add your own functionality and logic. It has been made easy to extend and modify according to your needs.

The bare minimum to get started is as follows, do not forget to create a service config file, see the Configurations tab, you can use the provided template config or create your own. Put at least the login, password, target_url, and cert fields in it.

Example service configuration
{
  "Version": "1.0.0",
  "Services": [
    {
      "Name": "mcx-client-app",
      "Enabled": true,
      "Parameters": {
        "Version": "1.0",
        "Children": []
      },
      "Config": {
        "login": "admin",
        "password": "vectioneer",
        "target_url": "wss://192.168.2.100",
        "cert": "mcx.cert.crt"
      },
      "Watchdog": {
        "Enabled": true,
        "Disabled": true,
        "high": 1000000,
        "tooHigh": 5000000
      }
    }
  ]
}

mcx-client-app.py:

import logging
logging.basicConfig(level=logging.INFO)

from src.mcx_client_app import McxClientApp, McxClientAppConfiguration, ThreadSafeValue

class ExampleMcxClientApp(McxClientApp):
    """
    Application that you can change to your needs.
    """
    def __init__(self, options: McxClientAppConfiguration):
        super().__init__(options)

        # Initiate your variables here


    def startOp(self) -> None:
        """
        Subscribe to button updates.
        """
        # Start your subscriptions here and set values after connection is established
        pass

    def iterate(self) -> None:
        """
        Increment counter and check for reset button press.
        """
        print("Client app is running...")
        self.wait(1)  # Wait 1 second between increments

    def onExit(self) -> None:
        """
        Clean up on exit.
        """
        # Unsubscribe from subscriptions here
        pass

if __name__ == "__main__":
    client_options = McxClientAppConfiguration()
    client_options.set_config_paths(
        deployed_config="/etc/motorcortex/config/services/mcx_client_app.json",  #This is only needed when deployed on a Motorcortex controller. If only locally running, you can set it to None.
        non_deployed_config="config.json"
    )

    app = ExampleMcxClientApp(client_options)
    app.run()

When you run this code, it creates a basic Motorcortex-python client application that connects to your specified Motorcortex server. By default, since no custom logic is added yet, the application will simply connect and repeatedly print “Client app is running…” to the console.

The run method manages the entire lifecycle of the connection: it connects to the Motorcortex server, engages the robot if needed, and then continuously calls your iterate method in a loop. When you stop the application, it will automatically disengage the robot and disconnect safely from the server.

To build your own functionality, you should implement the iterate method. This is where you can read parameters, send commands, or add any custom logic your client needs. You can also override other methods from the McxClientApp class to further customize your application’s behavior.

  • Use the startOp method to add logic that should run once when the client has connected to (for example, initializing parameters or setting up subscriptions).
  • Use the onExit method 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.

Subscribing to Parameters

To subscribe to parameters and receive updates, you can use the usual self.sub.subscribe. However, it is important to note that subscriptions should be created in the startOp method, which is called once after the connection is established. This ensures that the subscriptions are set up correctly and ready to receive updates. Additionally, you can use the notify method on the subscription to define a callback function that will be executed whenever the subscribed parameter changes but this happens in a different thread. Thus, be cautious when accessing shared data between threads.

The class ThreadSafeValue can be used to safely share data between the subscription callback and the main thread where iterate runs. This class has the methods get() and set(value) to access and modify the value in a thread-safe manner.

Running iterate in a Separate Thread: McxClientAppThread

If your client application needs to perform long-running or blocking operations in the iterate method, you can use the McxClientAppThread class instead of McxClientApp. With McxClientAppThread, the iterate method runs in its own thread, while the main thread continues to manage the connection and other logic. This separation allows your application to remain responsive, even if iterate takes time to complete.

To safely share data between the main thread and the iterate thread, use the provided ThreadSafeValue class. This utility ensures that data access is synchronized, preventing race conditions and ensuring data integrity.

Example of using McxClientAppThread with ThreadSafeValue:

from src.mcx_client_app import McxClientAppThread, McxClientAppConfiguration, ThreadSafeValue

class ToolPoseMonitorApp(McxClientAppThread):
    """
    Application that monitors and prints the tool pose.
    """
    def __init__(self, options: McxClientAppConfiguration):
        super().__init__(options)
        self.toolPoseSubscription = None
        self.toolPose: ThreadSafeValue[list[float]] = ThreadSafeValue([0, 0, 0, 0, 0, 0])

    def startOp(self) -> None:
        """
        Subscribe to tool pose updates.
        """
        self.toolPoseSubscription = self.sub.subscribe(
            'root/ManipulatorControl/manipulatorToolPoseActual',
            "Group1",
            frq_divider=1000
        )
        self.toolPoseSubscription.notify(lambda values: self.toolPose.set(values[0].value))

    def iterate(self) -> None:
        """
        Print current tool pose.
        """
        print(f"Toolpose {self.toolPose.get()}")
        self.wait(1)

if __name__ == "__main__":
    client_options = McxClientAppConfiguration()
    client_options.set_config_paths(
        deployed_config="/etc/motorcortex/config/services/mcx_client_app.json",  #This is only needed when deployed on a Motorcortex controller. If only locally running, you can set it to None.
        non_deployed_config="config.json"
    )

    app = ToolPoseMonitorApp(client_options)
    app.run()

In this example, the application subscribes to the tool pose parameter and prints its value every second. The ThreadSafeValue class is used to safely share the tool pose data between threads. The startOp method sets up the subscription after the connection is established, and the iterate method handles the periodic printing in its own thread.


Service Configuration

Service configuration files define how your client applications are configured and deployed on a Motorcortex controller. These JSON files specify connection parameters, watchdog settings, and custom configuration options for each service.

To register service configuration files, add them to the "Path" section in your project’s main config.json:

"Path": {
    "ServicesConfig": [
      "services/service_config.json"
    ],
}

Motorcortex will load and apply all configuration files listed in ServicesConfig. Each file uses the following structure to define services:

{
  "Version": "1.0.0",
  "Services": [
    {
      "Name": "mcx-client-app",
      "Enabled": true,
      "Parameters": {
        "Version": "1.0",
        "Children": []
      },
      "Config": {
        "login": "admin",
        "password": "vectioneer",
        "target_url": "wss://192.168.2.100",
        "cert": "mcx.cert.crt"
      },
      "Watchdog": {
        "Enabled": true,
        "Disabled": true,
        "high": 1000000,
        "tooHigh": 5000000
      }
    }
  ]
}

Field descriptions:

  • Version: Configuration file format version (e.g., "1.0.0").
  • Services: Array containing one or more service configurations.
    • Name: Unique service identifier. Must match the name parameter in your McxClientAppConfiguration.
    • 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 McxClientAppConfiguration or custom configuration class. These values are loaded when calling load_config().
    • Watchdog: Optional watchdog configuration for monitoring service health and performance thresholds.

All fields defined in the Config section are automatically loaded into your configuration class when you call load_config().

McxClientAppConfiguration

In the template examples, the class McxClientAppConfiguration is used to configure and initialize the McxClientApp. This class holds all the configuration options required to set up the client application. You can create an instance of this class and pass it to the McxClientApp constructor. The function set_config_paths is used to specify the paths to the configuration files for both deployed and non-deployed environments.

    client_options = McxClientAppConfiguration(name="ExampleMcxClientApp")
    client_options.set_config_paths(
        deployed_config="/etc/motorcortex/config/services/services_config.json",  #This is only needed when deployed on a Motorcortex controller. If only locally running, you can set it to None.
        non_deployed_config="services_config.json"
    )
    client_options.load_config()

    app = ExampleMcxClientApp(client_options)
    app.run()
More information

When deploying, it will automatically use the deployed configuration file, otherwise it will use the non-deployed configuration file. Behind the scenes, load_config looks for and environment variable called DEPLOYED, when this is True (set automatically when using makeDeb.py, see deploying for more information), it will use the deployed configuration file, otherwise it will use the non-deployed configuration file.

It is possible to not set the config paths and directly create an instance of McxClientAppConfiguration with the desired parameters. However, using configuration files is recommended for better manageability and flexibility, especially when deploying the application across different environments:

    client_options = McxClientAppConfiguration(
        login="admin",
        password="vectioneer",
        target_url="wss://192.168.2.100"
    )

    app = ExampleMcxClientApp(client_options)
    app.run()
Information on deployed_config (important)

The default path for deployed_config is setup so all the configs of services are stored in the service folder which you can deploy from the portal. This is recommended as it keeps all service configurations in one place and have a convenient way of deploying them. (If you do not have the services folder, you can create one.)

image not found
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 the iterate() method can run (default: None). If the system is not in one of these states, the iterate() method will not execute. If empty or None, the iterate() method can run in any state.
  • autoStart (bool): Whether the application should start automatically upon connection (default: True).
  • start_button_path (str|None): Parameter path for a start button to control auto-start behavior (default: None). If set, the application will use that path for start/stop control (default: root/serices/{name}/enableService)
  • enable_watchdog (bool): Whether to enable the watchdog functionality (default: True).
  • enable_error_handler (bool): Whether to enable the error handler functionality (default: True).
  • error_reset_param (str): Parameter path that indicates when to reset errors (default: root/Services/:fromState/resetErrors).
  • deployed_config (str): Path to the deployed configuration file (default: /etc/motorcortex/config/services/services_config.json).
  • non_deployed_config (str|None): Path to the non-deployed (local) configuration file. May be None by default.
Customizing McxClientAppConfiguration

You can create your own subclass of McxClientAppConfiguration to add custom configuration options specific to your client application. This allows you to extend the configuration capabilities while still leveraging the existing functionality of the base class. For example, when deployed, you can also change and deploy custom parameters via the portal.

To create a custom options class, simply inherit from McxClientAppConfiguration and add your own attributes and methods as needed. When inheriting from McxClientAppConfiguration, initialize any custom attributes first, then call super().__init__(**kwargs). This ensures the base-class initialization runs with your finalized attribute values. For example:

class CustomOptions(McxClientAppConfiguration):
    def __init__(self, custom_param: str = "default", **kwargs):
        self.custom_param = custom_param
        super().__init__(**kwargs)
Example Usage

This example will be a short snippet showing how to create a custom configuration class and use it with McxClientApp. We will also deploy it and then change the configuration via the portal.

In this example, we will add a custom parameter called print_integer that will be printed in the iterate() method of the client application.

  1. Firstly, create a custom configuration class by inheriting from McxClientAppConfiguration. We will add a custom parameter called print_integer.

    from src.mcx_client_app import McxClientApp, McxClientAppConfiguration
    import logging
    logging.basicConfig(level=logging.INFO)
    
    class CustomMcxClientAppConfiguration(McxClientAppConfiguration):
            """
            Custom configuration for the ExampleMcxClientApp.
    
            Attributes:
                print_integer (int): Number that will be printed in the iterate method.
            """
            def __init__(self, print_integer: int = 10, **kwargs):
                self.print_integer = print_integer
                super().__init__(**kwargs)
    
  2. Next, change the iterate method of the client application to print the custom parameter.

    class ExampleMcxClientApp(McxClientApp):
        """
        Application that you can change to your needs.
        """
        def __init__(self, options: McxClientAppConfiguration):
            super().__init__(options)
    
        def iterate(self) -> None:
            """
            Increment counter and check for reset button press.
            """
            self.wait(1)  # Wait 1 second between increments
            logging.info(f"Number: {self.options.print_integer}")
    
  3. Update the services_config.json with the following content (Do not forget to set your login credentials and target_url):

    {
      "Version": "1.0.0",
      "Services": [
        {
          "Name": "print-integer-client-app",
          "Enabled": true,
          "Config": {
            "login": "",
            "password": "",
            "target_url": "",
            "print_integer": 42
          }
        }
      ]
    }
    

    Here, we set the print_integer parameter to 42.

  4. Finally, create an instance of the custom configuration class using the set_config_paths helper method and pass it to the ExampleMcxClientApp constructor.

    if __name__ == "__main__":
        client_options = CustomMcxClientAppConfiguration()
        client_options.set_config_paths(
            deployed_config="/etc/motorcortex/config/services/services_config.json",  #This is only needed when deployed on a Motorcortex controller. If only locally running, you can set it to None.
            non_deployed_config="services_config.json"
        )
    
        app = ExampleMcxClientApp(client_options)
        app.run()
    

    When you run this application, it will print the value of print_integer (which is 42 in this case) every second.

    INFO:root:Waiting for start signal...
    INFO:root:Running user iterate...
    INFO:root:Number: 67
    INFO:root:Number: 67
    INFO:root:Number: 67
    INFO:root:Number: 67
    INFO:root:Number: 67
    INFO:root:Number: 67
    INFO:root:Number: 67
    ^CINFO:root:Keyboard interrupt received. Shutting down...
    
  5. Go to the project in the Motorcortex portal where you want to test out the script and navigate to the “.conf” folder. Create a new folder called ‘services’ if it does not exist yet. Then, change, or create, the services_config.json to include in the services list:

    {
      "Name": "mcx-client-app",
      "Enabled": true,
      "Parameters": {
        "Version": "1.0",
        "Children": []
      },
      "Config": {
        "login": "admin",
        "password": "vectioneer",
        "target_url": "wss://192.168.2.100"
      },
      "Watchdog": {
        "Enabled": true,
        "Disabled": true,
        "high": 1000000,
        "tooHigh": 5000000
      }
    }
    

    Then deploy the .conf folder. The deployed configuration file will be used instead of the local file when the application is deployed.

    See the top to

  6. Create a debian package for deployment, see the deploying guide for more information on deployment, and deploy it via the Motorcortex portal in any project.

  7. Restart Motorcortex and see the output in the services section of Cockpit.


Start button

You can setup a client app to start and stop its iterate method using a button in the Motorcortex GUI. By default, the McxClientApp class automatically starts but this can be disabled. Then you can hook a button up to enableService parameter to start and stop the iterate method.

  1. In your services_config.json file, set the autoStart parameter to false:
{
  "PACKAGE_NAME": {
    "Config": {
      "autoStart": false
    }
  }
}
  1. Create a switch in the Motorcortex GUI that is linked to the root/services/PACKAGE_NAME/enableService boolean parameter. When this switch is toggled on, the iterate method will start executing. When toggled off, it will stop.

Other Custom buttons

Other buttons with other logic can be added using the built-in commandWord parameter. The commandWord parameter is automatically created at root/Services/{ServiceName}/commandWord and allows external systems or GUI buttons to send commands to your service.

For this example, the basic Motorcortex Anthropomorphic Robot application is used. You can download it from the Motorcortex Store. Make sure to have the Motorcortex Anthropomorphic Robot application running and that you can connect to it using the DESK-Tool. But other Motorcortex applications will work as well.

This example shows how to use the commandWord parameter to reset a counter when a specific command is received. Send a command by setting commandWord to a specific value (e.g., 1 for reset, 2 for double).

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)
  1. In services_config.json, define your service parameters:
{
  "Name": "CustomButtonExample",
  "Enabled": true,
  "Parameters": {
    "Version": "1.0",
    "Children": [
      {
        "Name": "Counter",
        "Type": "int, parameter_volatile",
        "Value": 0
      }
    ]
  },
  "Config": {
    "login": "admin",
    "password": "vectioneer",
    "target_url": "wss://192.168.2.100",
    "autoStart": true
  },
  "Watchdog": {
    "Enabled": true,
    "Disabled": true,
    "high": 2000000,
    "tooHigh": 5000000
  }
}
  1. Create your application code:

Step 1: Import required modules and set up logging

from src.mcx_client_app import McxClientApp, McxClientAppConfiguration
from src.mcx_client_app.ChangeDetector import ChangeDetector
import motorcortex
import logging

logging.basicConfig(level=logging.INFO)

Step 2: Initialize the application class

class CustomButtonApp(McxClientApp):
    """
    Application that counts and processes commands via commandWord parameter.

    This example demonstrates using the ChangeDetector class to monitor the commandWord parameter
    and process commands only when the value changes.
    """
    def __init__(self, options: McxClientAppConfiguration):
        super().__init__(options)
        self.command_detector: ChangeDetector = ChangeDetector()
        self.counter: int = 0
        self.command_subscription = None

The ChangeDetector helps track when commandWord changes, so we only process commands when a new value is set (not on every subscription update).

Step 3: Subscribe to commandWord in startOp

    def startOp(self) -> None:
        """
        Subscribe to commandWord updates and initialize counter.
        """
        # Subscribe to commandWord parameter
        self.command_subscription = self.sub.subscribe(
            [f'{self.options.get_parameter_path}/commandWord'],
            "commandWord_group",
            frq_divider=10
        )
        result = self.command_subscription.get()
        if result and result.status == motorcortex.OK:
            self.command_subscription.notify(self._on_command_update)

        # Initialize counter parameter
        self.req.setParameter(f'{self.options.get_service_parameter_path}/Counter', 0).get()
  • Subscribe to commandWord at root/Services/CustomButtonExample/commandWord
  • The callback _on_command_update will be called whenever the parameter tree updates
  • Initialize the Counter parameter 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 returns True when commandWord changes to a non-zero value
  • Process different commands based on the value (1 = reset, 2 = double)
  • Important: Acknowledge the command by resetting commandWord back 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:

  1. External system sets commandWord to 1 (via GUI button or DESK)
  2. Subscription callback updates ChangeDetector with the new value
  3. iterate() detects the change and processes command 1 (reset counter)
  4. Application acknowledges by setting commandWord back to 0
  5. Ready for the next command

To ensure Motorcortex handles unexpected crashes or freezes of your client application gracefully, you can implement a watchdog mechanism. This involves periodically sending heartbeat signals from your client application to Motorcortex, indicating that it is running correctly.

In the standard McxClientApp, a watchdog is already implemented by default. It sends heartbeat signals at regular intervals to Motorcortex. If Motorcortex does not receive a heartbeat within a specified timeout period, it assumes the client application has crashed or frozen and takes appropriate action, such as throwing a warning or even throwing an emergency stop.

Watchdog Configurations

In the services_config.json file, you can configure the watchdog settings for your client application:

{
  "PACKAGE_NAME": {
    "Config": {
      "enable_watchdog": true
    },
    "Watchdog": {
      "Enabled": true,
      "Disabled": true,
      "high": 1000000,
      "tooHigh": 5000000
    }
  }
}

The enable_watchdog parameter in the Config section enables or disables the watchdog functionality in the client app. While the Watchdog section contains specific settings for the watchdog behavior in Motorcortex:

  • Enabled: If set to true, the watchdog is active and monitors the heartbeat signals.
  • Disabled: If set to true, the watchdog is inactive and does not monitor heartbeat signals.
  • high: The threshold (in microseconds) for a delayed heartbeat. If the heartbeat is delayed beyond this value, Motorcortex will log a warning.
  • tooHigh: The threshold (in microseconds) for a missed heartbeat. If the heartbeat is not received within this value, Motorcortex will assume the client application has crashed and take emergency action.

The McxErrorHandler allows your client application to trigger system-level errors at different severity levels in Motorcortex. This is essential for integrating your application into the overall system safety and error management.

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_id parameter when triggering errors to distinguish between subsystems
class FleetManager(McxClientApp):
    def __init__(self, options):
        super().__init__(options)
        self.ROBOT_1_SUBSYSTEM = 10
        self.ROBOT_2_SUBSYSTEM = 11
        self.ROBOT_3_SUBSYSTEM = 12

    def check_robot_1(self):
        if battery_low:
            # Specify which robot has the error
            self.errorHandler.trigger_warning(
                error_code=1001,
                subsystem_id=self.ROBOT_1_SUBSYSTEM
            )

Subsystem ID Guidelines:

  • Use unique IDs across your entire system to avoid conflicts
  • Document your subsystem ID allocation (e.g., 1-10 for service A, 11-20 for service B)
  • Reserve ID 0 for system-level errors (undefined subsystem)
  • Use consistent IDs - don’t change them between runs

Error Codes - Describing What Happened

Error codes describe the specific condition that triggered the error. Think of them as unique identifiers for different failure modes.

Example

class BatteryMonitor(McxClientApp):
    # Error code definitions
    ERROR_BATTERY_LOW = 1001
    ERROR_BATTERY_CRITICAL = 1002
    ERROR_CHARGING_FAULT = 1003
    ERROR_TEMPERATURE_HIGH = 1004
    ERROR_COMMUNICATION_LOST = 1005

    def check_battery(self):
        if battery_voltage < self.CRITICAL_THRESHOLD:
            self.errorHandler.trigger_emergency_stop(
                error_code=self.ERROR_BATTERY_CRITICAL
            )
        elif battery_voltage < self.LOW_THRESHOLD:
            self.errorHandler.trigger_warning(
                error_code=self.ERROR_BATTERY_LOW
            )

Example

1. Configure Error Handler in startOp():

This step sets up the error handler for your service. By assigning a subsystem ID, you ensure that all errors triggered by this instance are correctly attributed in the system. Setting an acknowledgment callback allows your app to respond when an operator clears an error, making it possible to reset state or resume operations safely.

def startOp(self) -> None:
    """Initialize error handler with subsystem ID."""
    # Set subsystem ID for this service
    self.errorHandler.set_subsystem_id(1)

    # Optional: Set callback for error acknowledgment
    self.errorHandler.set_acknowledge_callback(self.on_error_acknowledged)

    logging.info("Error handler configured for subsystem 1")

2. Trigger Errors in Your Code:

In your main loop or monitoring function, you check system conditions (like battery voltage) and use the error handler to report issues. Each call to the error handler includes an error code, which helps identify the specific problem. Severity is chosen based on the condition—critical errors trigger an emergency stop, while less severe issues trigger warnings.

def iterate(self) -> None:
    """Monitor conditions and trigger appropriate errors."""
    battery_voltage = self.get_battery_voltage()

    if battery_voltage < 10.5:
        # Critical - stop everything immediately
        self.errorHandler.trigger_emergency_stop(error_code=1002)

    elif battery_voltage < 11.0:
        # Low battery warning
        self.errorHandler.trigger_warning(error_code=1001)

    self.wait(1.0)

3. Handle Error Acknowledgment (Optional):

When an error is acknowledged by the operator (for example, after resolving the issue), the callback is invoked. Here, you can reset counters, clear flags, or perform any recovery logic needed to bring your application back to a safe state.

def on_error_acknowledged(self) -> None:
    """Called when operator acknowledges the error."""
    logging.info("Error acknowledged - resetting application state")

    # Reset your error conditions
    self.error_count = 0
    self.retry_attempts = 0

    # Clear any error flags
    self.req.setParameter(
        f"{self.options.get_service_parameter_path}/ErrorActive",
        0
    ).get()

Packaging your Client Application for Deployment

Instead of running the client application on a seperate pc that connects to the Motorcortex server over the network, you can also deploy the client application directly to the Motorcortex application. This can be useful for standalone setups or when you want to ensure that the client application is always running alongside Motorcortex.

A docker container is used to create and package the client application into a .deb file that can be deployed to the Motorcortex application. This container provides a consistent environment for building the package, ensuring that all necessary dependencies and configurations are included. Optionally, you can also use a python script to create the .deb on your local machine without using docker but additional python dependencies might be broken on the motorcortex application when deployed.

After creating a .deb file, it can be deployed to the controller using the Portal or manually via SSH after which you should install the package using dpkg -i <package>.deb.

For convenience, all deployment-related configurations can be specified in a single package_config.json file.


The first step in deploying your client application is to create a package_config.json file that contains the necessary configuration for building the Debian package. This file specifies various options such as the package name, the main Python script to run, any additional Python modules required, and other settings related to the deployment.

All the file paths specified in the package_config.json file should be relative to the root of your client application project from where you will run the deployment command.

An example package_config.json file is shown below:

{
  "PACKAGE_NAME": "mcx-client-app",
  "PYTHON_SCRIPT": "mcx-client-app.py",
  "PYTHON_MODULES": "src",
  "VERSION": "1.0",
  "MAINTAINER": "Vectioneer B.V. <info@vectioneer.com>"
}

Required options

  • PACKAGE_NAME: The name of the Debian package (e.g., “mcx-client-app”). Stick to lowercase letters, numbers, and hyphens!
  • PYTHON_SCRIPT: The main Python script file to execute (e.g., “mcx-client-app.py”).
  • PYTHON_MODULES: Space-separated list of Python module directories to include in the package (e.g., “src utils”).

Optional options

  • DEPLOY_MODE: Deployment mode, either container or venv (not recommended). In container mode, the package is designed to run inside a Docker container. In venv mode, the package creates a virtual environment on the target system (Backwards compatible with older MCX images). (Default: container).
  • VERSION: The version of the package (default: from default config).
  • ARCHITECTURE: The architecture of the package (default: from default config).
  • MAINTAINER: The maintainer of the package (default: from default config).
  • DESCRIPTION: A description of the package (default: from default config).
  • SERVICE: The systemd service name (default: same as PACKAGE_NAME).
  • BUILDFOLDER: The build folder path (default: from default config).
  • VENV_REQ_DIR: Directory for virtual environment requirements (default: from default config).
  • VENV_TARGET_DIR: Target directory for the virtual environment (default: “/usr/local/.venv.{PACKAGE_NAME}").
  • REQUIREMENTS_FILE: Path to the requirements.txt file (default: from default config, null will not download any wheels).
  • SERVICE_TEMPLATE: Path to the systemd service template file (default: “/usr/local/bin/template.service.in”).
  • DEBUG_ON: Enable debug output (default: false).

Any missing option will fall back to the default.

Service Template

By default, a generic systemd service template is used to create the service file for your client application. This template is located at /usr/local/bin/template.service.in inside the Docker container used for building the package. If you want to customize the service file, you can provide your own template by specifying the SERVICE_TEMPLATE option in the package_config.json file.

The custom service template should follow the systemd service file format, see Understand systemd service files. The default template can be seen below for reference:

[Unit]
Description=@DESCRIPTION@
After=ethercat.target motorcortex.service
PartOf=motorcortex.service
StartLimitIntervalSec=0

[Service]
Type=simple
ExecStartPre=/bin/sleep 3
ExecStart=@EXEC_START@
WorkingDirectory=/home/admin
User=admin
Restart=always
RestartSec=1

[Install]
WantedBy=motorcortex.service

Make sure you have Docker installed on your system. You can download and install Docker from the official Docker website.

Build a Debian Package Using the Docker Container

To build your client application into a Debian package using Docker, follow these steps. This method ensures a consistent build environment and isolates the build process from your local system.

  1. Build the Docker Image
    Navigate to the deploying directory 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 the deploying directory.

  2. 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 your package_config.json configuration 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 /workspace inside 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.

  3. Locate the Output
    After the build completes successfully, the resulting .deb package will be created in the build folder within your project directory. You can then upload this package to your Motorcortex binaries portal for deployment.

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

Build a Debian Package Without Using Docker

If you still choose to build locally, follow these steps:

  1. Configure the Service Template
    When building locally, you must specify the path to the systemd service template in your package_config.json file. This ensures the package includes the correct service configuration. Add this option to your config file to point to the template located in the Docker-build directory:

     "SERVICE_TEMPLATE": "Docker-build/template.service.in"
    
  2. Run the Build Script
    Execute the makeDeb.py script 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.json
    

    Replace package_config.json with the path to your configuration file if it’s named differently.


The template contains instructions for Copilot to use, so Copilot can help you code faster with AI. It knows precisely how the client app works and how it can interact with the Motorcortex controller. For the best results, be concrete in your prompts and provide as much context as possible. If you want a parameter ask for it directly and if it is an input or output in the perspective of the client app. Try to build an app in steps, starting with a basic version and then adding features in subsequent prompts to not overwhelm the AI.

Copilot looks automatically for instructions in the .github folder that is added in the Template. This folder contains a file called copilot-instructions.md with detailed instructions on how to use Copilot to create Motorcortex client applications. Even though the instructions are quite detailed, it is still important to provide context in the prompt itself to get the best results.

Click to show `copilot-instructions.md`

Note Hyperlinks may not work here, but you can view the file directly in the template repository.

Motorcortex Client Application (MCX-Client-App) - AI Coding Instructions

CRITICAL: READ THESE INSTRUCTIONS COMPLETELY BEFORE GENERATING ANY CODE

This document provides comprehensive servicesdance for AI agents to create, modify, and understand Motorcortex client applications. These are Python applications that connect to a Motorcortex control system to monitor parameters, control robots, automate workflows, and integrate with external systems.


Table of Contents

  1. How Client Apps Interact with Motorcortex
  2. Project Overview
  3. Core Architecture
  4. Creating an MCX-Client-App
  5. Configuration System
  6. Thread vs Non-Thread Usage
  7. Subscription Patterns
  8. Error Handler - Triggering System Errors
  9. Keep iterate() Clean - Best Practices
  10. Complete Working Examples
  11. API Reference
  12. 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 Parameters section of services_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 Config section of services_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 as root/Services/{Name})
  • Enabled: Whether service is enabled
  • Config: Runtime configuration (login, password, target_url, autoStart, custom fields) - access via self.options.field_name
  • Parameters: Service-specific parameters (placed under root/Services/{Name}/serviceParameters/) - access via self.options.get_service_parameter_path
  • Watchdog: 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">&#34;</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&#34;</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">&#34;</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&#34;</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:

  • input parameters - Client can call self.req.setParameter()
  • parameter parameters - Client can call self.req.setParameter()
  • parameter_volatile parameters - Client can call self.req.setParameter() (RECOMMENDED for client outputs)
  • output parameters - 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 press 0 or 1
  • int[3], input → Client writes array: [1, 2, 3]
  • double, output → Target writes, client reads: Sensor value 42.5
  • string, parameter → Configuration: "default_name"
  • int, parameter_volatile → Client app output that updates frequently: Counter value 42

Best Practices

DO:

  • Document required parameters in file docstring

  • Use descriptive names, organize hierarchically under UserParameters

  • Use parameter_volatile type for client app outputs (data that changes frequently, written by client)

  • Use input type for control commands (buttons, commands that trigger actions on Target)

  • Use output type 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 under root/UserParameters/
  • Use output type for values your client app writes (use parameter_volatile instead)
  • Use input for frequently changing client outputs (use parameter_volatile for better performance)
  • Try to call setParameter() on output type 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">&#34;root/AGVControl/actualVelocityLocal&#34;</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-python library)
  • Control robots using the robot_control API
  • Monitor and set parameters in real-time
  • Execute automated workflows with start/stop control
  • Deploy as Debian packages on MCX-RTOS systems

Key Files:

CRITICAL: Always Update Configuration Files When Making Changes

When you modify the project structure, immediately update the relevant configuration files:

  1. 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
    }
    
  2. 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
    }
    
  3. 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_SCRIPT in service_config.json
  • Changing package name for deployment → Update PACKAGE_NAME in service_config.json
  • Adding custom configuration options → Add fields to config.json AND custom Configuration class
  • Renaming the main Python file → Update PYTHON_SCRIPT in 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:

  1. __init__(self, options: McxClientAppConfiguration) - Initialize instance variables
  2. startOp(self) -> None - Called after connection, before iterate starts (setup subscriptions here)
  3. iterate(self) -> None - Main application logic (called repeatedly while running)
  4. 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 calling self.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): Undefined
  • INFO (1): Information message
  • WARNING (2): Warning - does not stop system
  • FORCED_DISENGAGE (3): Graceful software stop
  • SHUTDOWN (4): Abrupt software stop
  • EMERGENCY_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: If true, app starts immediately. If false, waits for enableService parameter
  • Watchdog.Enabled: Enable watchdog monitoring (recommended: true)
  • Watchdog.high: Warning threshold in microseconds
  • Watchdog.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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;

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">&#34;</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&#34;</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">&#34;Counter parameter initialized.&#34;</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">&#34;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">&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;

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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;

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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;

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">&#34;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.&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;

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">&#34;root/Logic/stateCommand&#34;</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">&#34;root/Logic/state&#34;</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_url and cert
  • Deployed: Uses target_url_deployed and cert_deployed, loads from CONFIG_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: true in Config → app starts immediately when service is enabled
  • Set autoStart: false in Config → app waits for enableService parameter 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() and wait_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 running state independently
  • Thread stops when running becomes False

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">&#34;Failed to subscribe to reset button&#34;</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">&#34;root/Sensors/Value&#34;</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">&#34;sensor&#34;</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">&#34;&#34;&#34;Runs in subscription thread&#34;&#34;&#34;</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">&gt;</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">&#34;&#34;&#34;Runs in main thread&#34;&#34;&#34;</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">&#34;Alarm! Sensor value: </span><span style="color:#4e9a06">{</span><span style="color:#000">current</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">&#34;</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">&#34;Unsubscribed from sensors&#34;</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">&#34;Error unsubscribing: </span><span style="color:#4e9a06">{</span><span style="color:#000">e</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">&#34;</span><span style="color:#000;font-weight:bold">)</span>

Subscription Best Practices

  1. Subscribe in startOp(), not __init__() - Connection must be established first
  2. Unsubscribe in onExit() - Clean up resources
  3. Keep callbacks EXTREMELY fast - Callbacks run in subscription thread, don’t block
  4. Use ThreadSafeValue - For data shared between subscription callbacks and iterate()
  5. Check subscription result - Always verify result.status == motorcortex.OK (import motorcortex first)
  6. GROUP parameters in subscriptions whenever possible - More efficient, better performance
  7. Use descriptive group_alias - Helps with debugging
  8. 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 from root/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">&#34;root/UserParameters/Config/MaxSpeed&#34;</span><span style="color:#000;font-weight:bold">,</span>
        <span style="color:#4e9a06">&#34;root/UserParameters/Config/Thresholds&#34;</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">&#34;config&#34;</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:

  1. Extract value from msg[0].value[0]
  2. Store it in a ThreadSafeValue: self.my_value.set(value)
  3. (Optional) Check if changed: if value != self.previous_value
  4. 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">&#34;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">&#34;</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">&#34;Velocity: </span><span style="color:#4e9a06">{</span><span style="color:#000">velocity</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">&#34;</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">&#34;root/Status/LastVelocity&#34;</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">&#34;velocity_log.txt&#34;</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#4e9a06">&#34;a&#34;</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">&#34;</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">&#34;</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">&#34;Velocity: </span><span style="color:#4e9a06">{</span><span style="color:#000">velocity</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">&#34;</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">&#34;root/Status/LastVelocity&#34;</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_id parameter 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">&lt;</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">&lt;</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">&#34;Error handler configured for subsystem 1&#34;</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">&lt;</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">&lt;</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">&#34;</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&#34;</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">&#34;normal&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Configure error handler.&#34;&#34;&#34;</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">&#34;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">&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Monitor safety parameters.&#34;&#34;&#34;</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">&#34;root/Sensors/Temperature&#34;</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">&#34;root/Sensors/Pressure&#34;</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">&#34;root/Safety/EmergencyStop&#34;</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">&#34;Emergency stop button pressed!&#34;</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">&gt;</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">&#34;critical&#34;</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">&#34;Temperature critical: </span><span style="color:#4e9a06">{</span><span style="color:#000">temperature</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">°C&#34;</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">&#34;critical&#34;</span>

    <span style="color:#204a87;font-weight:bold">elif</span> <span style="color:#000">temperature</span> <span style="color:#ce5c00;font-weight:bold">&gt;</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">&#34;warning&#34;</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">&#34;Temperature high: </span><span style="color:#4e9a06">{</span><span style="color:#000">temperature</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">°C&#34;</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">&#34;warning&#34;</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">&#34;normal&#34;</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">&lt;</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">&gt;</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">&#34;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&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Reset state when error is acknowledged.&#34;&#34;&#34;</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">&#34;Error acknowledged - resetting safety monitor&#34;</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">&#34;normal&#34;</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 -&gt; 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">&lt;</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">&lt;</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">&#34;root/Sensors/Pressure&#34;</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">&gt;</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">&#34;root/Safety/Alarm&#34;</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">&#34;Pressure alarm!&#34;</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">&#34;&#34;&#34;State machine - clean and readable.&#34;&#34;&#34;</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">&#34;Starting operation&#34;</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() or self.wait_for() for delays
  • Delegates to private helper methods (prefix with _)
  • Clear control flow (state machine if complex)
  • No initialization code (use __init__() or startOp())

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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Initialize counter parameter.&#34;&#34;&#34;</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">&#34;</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&#34;</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">&#34;Counter initialized&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Increment counter every second.&#34;&#34;&#34;</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">&#34;</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&#34;</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">&#34;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">&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Log final count.&#34;&#34;&#34;</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">&#34;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">&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Subscribe to temperature parameter.&#34;&#34;&#34;</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">&#34;root/Sensors/Temperature&#34;</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">&#34;temperature&#34;</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">&#34;Temperature subscription active&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Update current temperature (runs in subscription thread).&#34;&#34;&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Check temperature and control alarm.&#34;&#34;&#34;</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">&gt;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Activate alarm and log warning.&#34;&#34;&#34;</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">&#34;root/Safety/TemperatureAlarm&#34;</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">&#34;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&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Deactivate alarm.&#34;&#34;&#34;</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">&#34;root/Safety/TemperatureAlarm&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Cleanup subscriptions.&#34;&#34;&#34;</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">&#34;root/Safety/TemperatureAlarm&#34;</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">&#34;Temperature monitor stopped&#34;</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 &gt; 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):
    &quot;&quot;&quot;Executes pick-and-place motion program.&quot;&quot;&quot;

    def __init__(self, options: McxClientAppConfiguration):
        super().__init__(options)
        self.robot = None
        self.cycle_count = 0

    def startOp(self) -&gt; None:
        &quot;&quot;&quot;Initialize robot and engage.&quot;&quot;&quot;
        self.robot = RobotCommand(self.req, self.motorcortex_types)

        if self.robot.engage():
            logging.info(&quot;Robot engaged successfully&quot;)
            self.robot.stop()
            self.robot.reset()
        else:
            logging.error(&quot;Failed to engage robot&quot;)
            self.reset()  # Stop the application

    def iterate(self) -&gt; None:
        &quot;&quot;&quot;Execute pick-and-place cycle.&quot;&quot;&quot;
        self._executePickPlace()
        self.cycle_count += 1
        logging.info(f&quot;Completed cycle {self.cycle_count}&quot;)
        self.wait(2)

    def _executePickPlace(self) -&gt; None:
        &quot;&quot;&quot;Execute the pick-and-place motion program.&quot;&quot;&quot;
        # 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(&quot;pick_place_cycle&quot;).get()

        state = self.robot.play()
        if state == InterpreterStates.MOTION_NOT_ALLOWED_S.value:
            logging.info(&quot;Moving to start position...&quot;)
            if self.robot.moveToStart(10):
                self.robot.play()

        # Wait for completion
        self.wait_for(&quot;root/Control/fInterpreterState&quot;,
                     InterpreterStates.MOTION_COMPLETE_S.value,
                     timeout=30)

    def onExit(self) -&gt; None:
        &quot;&quot;&quot;Stop and disengage robot.&quot;&quot;&quot;
        if self.robot:
            self.robot.stop()
            self.robot.disengage()
            logging.info(f&quot;Robot stopped after {self.cycle_count} cycles&quot;)

if __name__ == &quot;__main__&quot;:
    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=&quot;/etc/motorcortex/config/services/robot_pick_place.json&quot;,
        non_deployed_config=&quot;config.json&quot;
    )
    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">&#34;&#34;&#34;
</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">    &#34;&#34;&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;
</span><span style="color:#4e9a06">        Initialize error handler with subsystem ID and acknowledge callback.
</span><span style="color:#4e9a06">        &#34;&#34;&#34;</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">&#34;Error handler configured&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;
</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">        &#34;&#34;&#34;</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">&#34;</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&#34;</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">&#34;Failed to reset input parameter after error acknowledgment.&#34;</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">&#34;Error acknowledged - input parameter reset to 0&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;
</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">        &#34;&#34;&#34;</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">&#34;</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&#34;</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">&lt;</span> <span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold">&lt;</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">&#34;Triggering WARNING level error.&#34;</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">&lt;=</span> <span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold">&lt;</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">&#34;Triggering FORCED_DISENGAGE level error.&#34;</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">&lt;=</span> <span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold">&lt;</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">&#34;Triggering SHUTDOWN level error.&#34;</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">&lt;=</span> <span style="color:#000">value</span> <span style="color:#ce5c00;font-weight:bold">&lt;</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">&#34;Triggering EMERGENCY_STOP level error.&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;
</span><span style="color:#4e9a06">        Cleanup on exit.
</span><span style="color:#4e9a06">        &#34;&#34;&#34;</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">&#34;Error handler app exiting&#34;</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">&#34;__main__&#34;</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">&#34;ErrorExample&#34;</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">&#34;/etc/motorcortex/config/services/services_config.json&#34;</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">&#34;services_config.json&#34;</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">&#34;Services&#34;</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">&#34;Name&#34;</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">&#34;ErrorExample&#34;</span><span style="color:#000;font-weight:bold">,</span>
      <span style="color:#204a87;font-weight:bold">&#34;Enabled&#34;</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">&#34;Config&#34;</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000;font-weight:bold">{</span>
        <span style="color:#204a87;font-weight:bold">&#34;login&#34;</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">&#34;admin&#34;</span><span style="color:#000;font-weight:bold">,</span>
        <span style="color:#204a87;font-weight:bold">&#34;password&#34;</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">&#34;password&#34;</span><span style="color:#000;font-weight:bold">,</span>
        <span style="color:#204a87;font-weight:bold">&#34;target_url&#34;</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">&#34;wss://192.168.1.100&#34;</span><span style="color:#000;font-weight:bold">,</span>
        <span style="color:#204a87;font-weight:bold">&#34;autoStart&#34;</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">&#34;Parameters&#34;</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000;font-weight:bold">{</span>
        <span style="color:#204a87;font-weight:bold">&#34;Version&#34;</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">&#34;1.0&#34;</span><span style="color:#000;font-weight:bold">,</span>
        <span style="color:#204a87;font-weight:bold">&#34;Children&#34;</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">&#34;Name&#34;</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">&#34;userParameters&#34;</span><span style="color:#000;font-weight:bold">,</span>
            <span style="color:#204a87;font-weight:bold">&#34;Children&#34;</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">&#34;Name&#34;</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">&#34;input&#34;</span><span style="color:#000;font-weight:bold">,</span>
                <span style="color:#204a87;font-weight:bold">&#34;Type&#34;</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#4e9a06">&#34;int, input&#34;</span><span style="color:#000;font-weight:bold">,</span>
                <span style="color:#204a87;font-weight:bold">&#34;Value&#34;</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">&#34;Watchdog&#34;</span><span style="color:#000;font-weight:bold">:</span> <span style="color:#000;font-weight:bold">{</span>
        <span style="color:#204a87;font-weight:bold">&#34;Enabled&#34;</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">&#34;Disabled&#34;</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">&#34;high&#34;</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">&#34;tooHigh&#34;</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">&#34;&#34;&#34;Configuration for data logger with custom parameters.&#34;&#34;&#34;</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">&#34;data_log.json&#34;</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">&#34;&#34;&#34;Logs specified parameters to file at regular intervals.&#34;&#34;&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;Subscribe to parameters to log.&#34;&#34;&#34;</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">&#34;No parameters configured for logging&#34;</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">&#34;data_logger&#34;</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">&#34;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&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;Update latest values from subscription.&#34;&#34;&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;Log current values to file.&#34;&#34;&#34;</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">&#34;timestamp&#34;</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">&#34;data&#34;</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">&#34;Logged: </span><span style="color:#4e9a06">{</span><span style="color:#000">log_entry</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;Save logged data to file.&#34;&#34;&#34;</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">&#34;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.&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;Write logged data to JSON file.&#34;&#34;&#34;</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">&#39;w&#39;</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">&#34;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">&#34;</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">&#34;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">&#34;</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">&#34;__main__&#34;</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">&#34;wss://192.168.1.100&#34;</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">&#34;sensor_data.json&#34;</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">&#34;root/Sensors/Temperature&#34;</span><span style="color:#000;font-weight:bold">,</span>
            <span style="color:#4e9a06">&#34;root/Sensors/Pressure&#34;</span><span style="color:#000;font-weight:bold">,</span>
            <span style="color:#4e9a06">&#34;root/Sensors/Humidity&#34;</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">&#34;/etc/motorcortex/config/services/data_logger.json&#34;</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">&#34;config.json&#34;</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">&#34;Undefined&#34;</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-&gt;</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">&#34;my_program&#34;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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">&#34;Robot engaged&#34;</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">&#34;Program is stopped&#34;</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">-&gt;</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">-&gt;</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">-&gt;</span> <span style="color:#000">Reply</span>
<span style="color:#8f5902;font-style:italic"># param_list format: [{&#34;path&#34;: &#34;...&#34;, &#34;value&#34;: ...}, ...]</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">-&gt;</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">&#34;root/Sensors/Temperature&#34;</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">&#34;root/Control/Speed&#34;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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">&#34;root/Sensors/Temperature&#34;</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#4e9a06">&#34;root/Sensors/Pressure&#34;</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">&#34;sensors&#34;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Called after connection, before iterate starts.&#34;&#34;&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Main application logic (called repeatedly).&#34;&#34;&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Cleanup before disconnect.&#34;&#34;&#34;</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">-&gt;</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">&#34;==&#34;</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-&gt;</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">-&gt;</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">-&gt;</span> <span style="color:#204a87">dict</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;
</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">    &#34;&#34;&#34;</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">&#34;Configuration path must be provided&#34;</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">&#34;[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">&#34;</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">&#39;r&#39;</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">&#34;Services&#34;</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">&#34;[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">&#34;</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">&#34;Name&#34;</span><span style="color:#000;font-weight:bold">,</span> <span style="color:#4e9a06">&#34;&#34;</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">&#34;[ERROR] No service with name &#39;</span><span style="color:#4e9a06">{</span><span style="color:#000">name</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">&#39; 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">&#34;</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">&#34;Config&#34;</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">&#34;[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.&#34;</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">&#34;&#34;&#34;
</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., &#39;wss://localhost&#39;).
</span><span style="color:#4e9a06">        target_url_deployed (str): Deployed WebSocket URL (default: &#39;wss://localhost&#39;).
</span><span style="color:#4e9a06">        cert (str): Local Development path to SSL certificate (e.g., &#39;mcx.cert.crt&#39;).
</span><span style="color:#4e9a06">        cert_deployed (str): Deployed path to SSL certificate (default: &#39;/etc/ssl/certs/mcx.cert.pem&#39;).
</span><span style="color:#4e9a06">        statecmd_param (str): Parameter path for state commands (default: &#39;root/Logic/stateCommand&#39;).
</span><span style="color:#4e9a06">        state_param (str): Parameter path for current state (default: &#39;root/Logic/state&#39;).
</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: &#39;root/Services/:fromState/resetErrors&#39;).
</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">    &#34;&#34;&#34;</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">&#34;wss://localhost&#34;</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">&#34;wss://localhost&#34;</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">&#34;mcx.cert.crt&#34;</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">&#34;/etc/ssl/certs/mcx.cert.pem&#34;</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">&#34;root/Logic/stateCommand&#34;</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">&#34;root/Logic/state&#34;</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">&#34;root/Services/:fromState/resetErrors&#34;</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">-&gt;</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">&#34;/etc/motorcortex/config/services/services_config.json&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;Load configuration from the set config paths.&#34;&#34;&#34;</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">&#34;run_during_states&#34;</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">&#34;Configuration loaded from </span><span style="color:#4e9a06">{</span><span style="color:#4e9a06">&#39;deployed&#39;</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">&#39;non-deployed&#39;</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">&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;Set the configuration file paths for deployed and non-deployed environments.&#34;&#34;&#34;</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">-&gt;</span> <span style="color:#204a87">bool</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;Check if running in deployed environment (checks DEPLOYED env var).&#34;&#34;&#34;</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">&#34;DEPLOYED&#34;</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">-&gt;</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;Get certificate path based on deployment status.&#34;&#34;&#34;</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">-&gt;</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;Get target URL based on deployment status.&#34;&#34;&#34;</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">-&gt;</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;Get parameter path root for the service.&#34;&#34;&#34;</span>
        <span style="color:#204a87;font-weight:bold">return</span> <span style="color:#4e9a06">f</span><span style="color:#4e9a06">&#34;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">&#34;</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">-&gt;</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;Get service parameter path root.&#34;&#34;&#34;</span>
        <span style="color:#204a87;font-weight:bold">return</span> <span style="color:#4e9a06">f</span><span style="color:#4e9a06">&#34;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&#34;</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">-&gt;</span> <span style="color:#204a87">str</span><span style="color:#000;font-weight:bold">:</span>
        <span style="color:#4e9a06">&#34;&#34;&#34;Get parameter path for start button control.&#34;&#34;&#34;</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">&#34;root/&#34;</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">&#34;</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">&#34;</span>
        <span style="color:#204a87;font-weight:bold">return</span> <span style="color:#4e9a06">f</span><span style="color:#4e9a06">&#34;</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&#34;</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">-&gt;</span> <span style="color:#204a87">str</span>          <span style="color:#8f5902;font-style:italic"># &#34;root/Services/{ServiceName}&#34;</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">-&gt;</span> <span style="color:#204a87">str</span>  <span style="color:#8f5902;font-style:italic"># &#34;root/Services/{ServiceName}/serviceParameters&#34;</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">-&gt;</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">-&gt;</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">-&gt;</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&rsquo;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">&#34;&#34;&#34;
</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">    &#34;&#34;&#34;</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">&#34;</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&#34;</span><span style="color:#000;font-weight:bold">],</span>
            <span style="color:#4e9a06">&#34;cmd&#34;</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">&#34;</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&#34;</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">&#34;&#34;&#34;Process commandWord values.&#34;&#34;&#34;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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">&#34;</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&#34;</span><span style="color:#000;font-weight:bold">],</span>
            <span style="color:#4e9a06">&#34;command_group&#34;</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">&#34;&#34;&#34;Callback for commandWord updates (runs in subscription thread).&#34;&#34;&#34;</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">&#34;Command received: </span><span style="color:#4e9a06">{</span><span style="color:#000">value</span><span style="color:#4e9a06">}</span><span style="color:#4e9a06">&#34;</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">&#34;Reset command received&#34;</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">&#34;Start command received&#34;</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">&#34;Stop command received&#34;</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">-&gt;</span> <span style="color:#204a87">bool</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Process sensor values against threshold.&#34;&#34;&#34;</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">-&gt;</span> <span style="color:#204a87;font-weight:bold">None</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;Callback receives motorcortex message (no type hint needed).&#34;&#34;&#34;</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">&#34;==&#34;</span><span style="color:#000;font-weight:bold">)</span> <span style="color:#ce5c00;font-weight:bold">-&gt;</span> <span style="color:#204a87">bool</span><span style="color:#000;font-weight:bold">:</span>
    <span style="color:#4e9a06">&#34;&#34;&#34;
</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 (&#34;==&#34;, &#34;!=&#34;, &#34;&lt;&#34;, &#34;&lt;=&#34;, &#34;&gt;&#34;, &#34;&gt;=&#34;, &#34;in&#34;).
</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">    &#34;&#34;&#34;</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">&#34;Robot engaged&#34;</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">&#34;Failed to engage robot&#34;</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">&#34;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">&#34;</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">&lt;</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">&#34;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">&#34;</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">&#34;Operation stopped by user&#34;</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">&#34;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">&#34;</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">&#34;&#34;&#34;Docstring.&#34;&#34;&#34;</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">&#34;&#34;&#34;Initialize.&#34;&#34;&#34;</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&rsquo;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&rsquo;s intent is unclear:</p>
<ul>
<li>What should the app do? (monitor, control, automate, log)</li>
<li>What parameters to interact with?</li>
<li>Should it use start/stop button or state-based execution?</li>
<li>Any custom configuration needed?</li>
</ul>
</li>
<li>
<p><strong>Start with the simplest solution</strong>:</p>
<ul>
<li>Use <code>McxClientApp</code> unless threading is explicitly needed</li>
<li>Use built-in <code>McxClientAppConfiguration</code> unless custom parameters needed</li>
<li>Keep <code>iterate()</code> focused and delegate to helper methods</li>
</ul>
</li>
<li>
<p><strong>Follow the structure</strong>:</p>
<ul>
<li>Custom configuration class (if needed)</li>
<li>Main application class inheriting from <code>McxClientApp</code> or <code>McxClientAppThread</code></li>
<li>Lifecycle methods: <code>__init__</code>, <code>startOp</code>, <code>iterate</code>, <code>onExit</code></li>
<li>Private helper methods for logic</li>
<li>Subscription callbacks</li>
<li>Main block with configuration and <code>app.run()</code></li>
</ul>
</li>
<li>
<p><strong>Include comprehensive docstrings</strong> - Other AI agents will read your code</p>
</li>
<li>
<p><strong>Add logging</strong> at key points for debugging</p>
</li>
<li>
<p><strong>Handle errors gracefully</strong> with try/except and logging</p>
</li>
<li>
<p><strong>Show config.json example</strong> with required parameters</p>
</li>
<li>
<p><strong>Explain what the app does</strong> in comments</p>
</li>
</ol>
<hr>
<p><strong>Remember:</strong> The goal is to create clean, maintainable, production-ready code that other developers (and AI agents) can understand and modify easily. Always double-check for mistakes and avoid duplicating functions or classes that already exist.</p>


Example

The following prompt was given to Copilot, running Claude Sonnot 4.5 model in agent mode in VS Code:

It subsequently edited the mcx-client-app.py file to to the following:

Click to show `mcx-client-app.py`
#
#   Developer : Coen Smeets (Coen@vectioneer.com)
#   All rights reserved. Copyright (c) 2025 VECTIONEER.
#

"""
MCX-Client-App: Quadratic Equation Processor

REQUIRED PARAMETERS:
Add this to the end of the parameters.json file in the config/user folder:

{
  "Name": "services",
  "Children": [
    {
      "Name": "QuadraticProcessor",
      "Children": [
        {
          "Name": "input",
          "Type": "double, input",
          "Value": 0.0
        },
        {
          "Name": "output",
          "Type": "double, parameter_volatile",
          "Value": 0.0
        }
      ]
    }
  ]
}

SETUP: 1) Add JSON above to parameters.json, 2) Restart Motorcortex, 3) Verify in DESK tool
"""

import sys
from pathlib import Path
import logging
logging.basicConfig(level=logging.INFO)

# Add parent directory to path to import mcx_client_app
sys.path.insert(0, str(Path(__file__).parent.parent))

from src.mcx_client_app import McxClientApp, McxClientAppConfiguration, ThreadSafeValue
import motorcortex


class QuadraticProcessorConfiguration(McxClientAppConfiguration):
    """
    Configuration for Quadratic Equation Processor with equation coefficients.
    
    Attributes:
        a (float): Coefficient for x^2 term (default: 1.0)
        b (float): Coefficient for x term (default: 0.0)
        c (float): Constant term (default: 0.0)
        input_param (str): Path to input parameter
        output_param (str): Path to output parameter
    """
    def __init__(
        self,
        a: float = 1.0,
        b: float = 0.0,
        c: float = 0.0,
        input_param: str = "root/UserParameters/services/QuadraticProcessor/input",
        output_param: str = "root/UserParameters/services/QuadraticProcessor/output",
        **kwargs
    ):
        self.a = a
        self.b = b
        self.c = c
        self.input_param = input_param
        self.output_param = output_param
        super().__init__(**kwargs)


class QuadraticProcessorApp(McxClientApp):
    """
    High-speed quadratic equation processor: y = ax^2 + bx + c
    Subscribes to input parameter and outputs result instantly.
    """
    def __init__(self, options: QuadraticProcessorConfiguration):
        super().__init__(options)
        self.options: QuadraticProcessorConfiguration  # Type hint for IDE
        
        # Thread-safe storage for input value
        self.current_input = ThreadSafeValue(0.0)
        self.input_subscription = None
        
        logging.info(f"Quadratic processor initialized: y = {self.options.a}x^2 + {self.options.b}x + {self.options.c}")
        
    
    def startOp(self) -> None:
        """
        Initialize output parameter and subscribe to input parameter for real-time updates.
        """
        # Initialize output parameter to zero
        try:
            self.req.setParameter(self.options.output_param, 0.0).get()
            logging.info(f"Output parameter initialized: {self.options.output_param}")
        except Exception as e:
            logging.error(f"Failed to initialize output parameter: {e}")
            raise
        
        # Subscribe to input parameter with high frequency for speed
        self.input_subscription = self.sub.subscribe(
            [self.options.input_param],
            group_alias="quadratic_input",
            frq_divider=1  # Maximum speed - update every cycle
        )
        
        result = self.input_subscription.get()
        if result and result.status == motorcortex.OK:
            self.input_subscription.notify(self._onInputUpdate)
            logging.info(f"Subscribed to input parameter: {self.options.input_param}")
        else:
            logging.error(f"Failed to subscribe to input parameter: {self.options.input_param}")
            self.reset()  # Stop the application

    def iterate(self) -> None:
        """
        Calculate quadratic equation and output result.
        Runs continuously for maximum throughput.
        """
        # Get current input value (thread-safe)
        x = self.current_input.get()
        
        # Calculate: y = ax^2 + bx + c
        y = self.options.a * (x ** 2) + self.options.b * x + self.options.c
        
        # Write output (parameter_volatile type optimized for frequent writes)
        try:
            self.req.setParameter(self.options.output_param, y).get()
        except Exception as e:
            logging.error(f"Failed to set output parameter: {e}")
        
        # Minimal wait for responsiveness - allows stop signal checking
        self.wait(0.001)  # 1ms for maximum speed while still responsive
        
    def onExit(self) -> None:
        """
        Clean up on exit - unsubscribe from input parameter.
        """
        if self.input_subscription:
            try:
                self.input_subscription.unsubscribe()
                logging.info("Unsubscribed from input parameter")
            except Exception as e:
                logging.error(f"Error unsubscribing: {e}")
        
        logging.info("Quadratic processor stopped")
        
if __name__ == "__main__":
    client_options = QuadraticProcessorConfiguration(
        a=1.0,   # Coefficient for x^2
        b=0.0,   # Coefficient for x
        c=0.0    # Constant term
    )
    client_options.set_config_paths(
        deployed_config="/etc/motorcortex/config/services/quadratic_processor.json",
        non_deployed_config="config.json"
    )

    app = QuadraticProcessorApp(client_options)
    app.run()

As can be seen, Copilot was able to create a complete client application that does exactly what was requested in the prompt, including description on how to set up the required parameters in the parameters.json file and comments throughout the code explaining each part of the implementation.