MotionController::Get in a docker env

We are running into a runtime error with using the MotionController::Get() within a docker container.

psdn_sm  | Terminate called after throwing an instance of std::runtime_error: Error! Linux x64: RMP is not started :: {platformOS.c, line 96}  : RMP firmware not started:  MotionController::Get() will not start RMP firmware. (Error 5139) (MotionController::InitControl) (Object 0) (File motioncontroller.cpp) (Line 442) (Version 10.6.7.0)

We are observing that the process in the container is failing to acquire the running motioncontroller.

We see that the /dev/shm is mounted in our container.

Can you confirm whether the RMP firmware was started inside the Docker container, or only on the host? If in a container, is the Get() in the same container?

We’ll try to reproduce this. We are also about to have a fix for MotionController::Get(), when the license file isn’t in the same folder as the executable calling Get().

We’ve tested multiple configs - but the most recent test we did that led us to believe it was the container itself: RMP firmware started via RapidSetupX in host, SW running in container = Fail, SW running on Host = Success

We’ve also tried other configs :
running the firmware in a container + sw in a container = Failure
running the firmware in a container + sw in host = Success

Also thanks for looking into the fix for licenses via Get()!

An update from our side - we managed to have our client sw connect from within a container by having both firmware + client containers use the host process namespace.

Good to know, I just reproduced your issue (with Python) and I’ll check out the process namespaces…

1 Like

Thanks for identifying the issue.

When MotionController::Get() is called, it checks whether the RMP firmware is already running by looking for its process using pgrep. This check relies on being in the same PID namespace as the firmware. Without --pid=host, the container is in its own isolated namespace and can’t see host processes—even if they’re running and accessible in other ways.

So, for any container that needs to attach to a running RMP instance via MotionController::Get(), --pid=host is required to ensure the firmware process is visible.

1 Like

Just to summarize my setup, testing MotionController::Get() from Python in Linux in a container:

Dockerfile

FROM python:3.11-slim

# install libusb and libyubikey RapidCode dependencies
# procps for pgrep for detecting RMP is running
RUN apt-get update && apt-get install -y \
  libusb-1.0-0 \
  libyubikey0 \
  procps \ 
  && rm -rf /var/lib/apt/lists/*

# Force NumPy 1.x for ABI compatibility
RUN pip install "numpy<2"

WORKDIR /app

hellormp.py

import os
import platform
import sys

sys.path.append("/rsi")
import RapidCodePython as RapidCode # The RapidCodePython.py wrapper located in your RMP install directory

def print_topology(controller:RapidCode.MotionController):
    stringBuilder = []
    overview = "EtherCAT: {} Nodes, {} {}".format(controller.NetworkNodeCountGet(), controller.NetworkTypeGet(), controller.NetworkStateGet())
    stringBuilder.append(overview)

    Nodes = []
    for i in range(controller.NetworkNodeCountGet()):
        io = controller.IOGet(i)
        check_errors(io)
        if(io.NetworkNode.Exists()):
            Nodes.append(io.NetworkNode)
        

    for node in Nodes:
        stringBuilder.append("") # for spacing
        stringBuilder.append("Node[{}] - {} ______________________________________________".format(node.NumberGet(), node.NameGet()))
        stringBuilder.append(" Vendor: {} ".format(node.VendorNameGet() + "Product: {}".format(node.ProductNameGet())))
        stringBuilder.append("  VendorID:      0x{:08X} ProductCode:   0x{:08X}".format(node.VendorIdGet(), node.ProductCodeGet()))
        stringBuilder.append("  HardwareRev:   0x{:08X} SerialNumber:  {}".format(node.RevisionGet(), node.SerialNumberGet()))
        stringBuilder.append("  StationAlias:  0x{:08X} AxisCount:     {}".format(node.StationAliasGet(), node.AxisCountGet()))
        stringBuilder.append("  SegmentCount:  {}".format(node.SegmentCountGet()))
        stringBuilder.append("  DI: {} DO: {} AI: {} AO: {}".format(node.DigitalInCountGet(), node.DigitalOutCountGet(), node.AnalogInCountGet(), node.AnalogOutCountGet()))

    stringBuilder.append("") # spacing
    pdoInputs = controller.NetworkInputCountGet()
    stringBuilder.append("NetworkInputs count: {} _________________________________".format(pdoInputs))
    for i in range(pdoInputs):
        stringBuilder.append("  [{}] - {} Bits: {}".format(i, controller.NetworkInputNameGet(i).ljust(70), controller.NetworkInputBitSizeGet(i)))

    pdoOutputs = controller.NetworkOutputCountGet()
    stringBuilder.append("NetworkOutputs count: {} _________________________________".format(pdoOutputs))
    for i in range(pdoOutputs):
        stringBuilder.append("  [{}] - {} Bits: {}".format(i, controller.NetworkOutputNameGet(i).ljust(70), controller.NetworkOutputBitSizeGet(i)))

    print("\n".join(stringBuilder))


def check_errors(rsi_object):
    """
    Check for errors in the given rsi_object and print any errors that are found. If the error log contains any errors (not just warnings), raises an exception with the error log as the message.
    Returns a tuple containing a boolean indicating whether the error log contained any errors and the error log string.
    """
    error_string_builder = ""
    i = rsi_object.ErrorLogCountGet()
    while rsi_object.ErrorLogCountGet() > 0:
        error:RapidCode.RsiError = rsi_object.ErrorLogGet()
        error_type = "WARNING" if error.isWarning else "ERROR"
        error_string_builder += f"{error_type}: {error.text}\n"
        if len(error_string_builder) > 0:
            print(error_string_builder)
        if "ERROR" in error_string_builder:
            raise Exception(error_string_builder)
    return "ERROR" in error_string_builder, error_string_builder


## @cond
def main():
    print("Hello, RapidCode.")

    motionController:RapidCode.MotionController = RapidCode.MotionController.Get()
    print("This object is a "+str(type(motionController)))#When creating your RapidCode objects (Axis, MultiAxis, MotionController,ect) we recommend specifying the type with my_object_name:RapidCode.*** (MotionController,Axis, Ect.) so that your IDE can autocomplete class member names. 
    
    print("MotionController creation error count: ", motionController.ErrorLogCountGet())
    check_errors(motionController)
    
    print("RapidCode Version: ", motionController.VersionGet())
    print("Serial Number: ", motionController.SerialNumberGet())
    print("Axis Count: ", motionController.AxisCountGet())
    
    if motionController.NetworkStateGet() == RapidCode.RSINetworkState_RSINetworkStateOPERATIONAL:
        print_topology(motionController)

    motionController.Delete()

if __name__ == "__main__":
    main()

Build the container
docker build -t rmp-python .

Run the container:

docker run --rm -it \                          # Remove container on exit, run interactive terminal
  --privileged \                              # Full access to host devices and capabilities
  --net=host \                                # Share host network stack (needed for real-time comms)
  --pid=host \                                # Share host PID namespace (needed to see firmware process)
  -v /:/host \                                # Mount full host filesystem as /host
  -v /rsi:/rsi \                              # Mount RSI libraries and wrappers
  -v "$PWD":"$PWD" \                          # Mount current working directory
  -v /dev/shm:/dev/shm \                      # Share host shared memory (used by RMP)
  -w "$PWD" \                                 # Set working directory inside container
  rmp-python \                                # Image name
  python hellormp.py                          # Run the target script
1 Like