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.
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.
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