# MTranServer ROS2 English-to-Chinese Service Design ## 1. Goal Build a ROS2 Humble C++ package that exposes one synchronous service for translating a single English sentence into Simplified Chinese. The package runs on Ubuntu 22.04, is built with the current machine's native toolchain, and starts a persistent MTranServer sidecar through ROS2 launch. All new source, documentation, runtime staging files, and build outputs for this migration live under `./migrate_ws`. ## 2. Scope The package provides exactly one operation: ```text English string in a ROS2 service request -> MTranServer en-to-zh-Hans translation -> Chinese string in the ROS2 service response ``` The following features are deliberately excluded: - Topic subscription or publication - Batch translation - Runtime language selection - Automatic language detection - HTML translation - Long-text splitting - Web UI integration - Native C++ Bergamot integration ## 3. Target Environment - ROS2 Humble - Ubuntu 22.04 - C++17 - `ament_cmake` and `colcon` - libcurl for HTTP - nlohmann/json for JSON - Bun only for producing the MTranServer sidecar executable The same ROS2 source is compiled natively on x64 or ARM64. The current implementation and smoke test use the current machine's native MTranServer artifact. ARM64 MTranServer build and target-device deployment commands are documentation-only in this iteration. ## 4. Workspace Layout ```text migrate_ws/ ├── docs/ │ └── superpowers/ │ ├── specs/ │ └── plans/ ├── models/ │ └── en_zh-Hans/ ├── runtime/ │ └── bin/ │ └── mtranserver ├── src/ │ └── mtran_ros2/ │ ├── CMakeLists.txt │ ├── package.xml │ ├── config/ │ │ └── translator.yaml │ ├── include/mtran_ros2/ │ │ ├── http_translation_client.hpp │ │ └── translator_node.hpp │ ├── launch/ │ │ └── translator.launch.py │ ├── scripts/ │ │ └── build_mtranserver.sh │ ├── src/ │ │ ├── http_translation_client.cpp │ │ ├── main.cpp │ │ └── translator_node.cpp │ ├── srv/ │ │ └── TranslateEnglishToChinese.srv │ └── test/ │ ├── test_http_translation_client.cpp │ └── test_translator_node.cpp └── README.md ``` Generated `build/`, `install/`, `log/`, the local MTranServer executable, and translation model files are not committed. ## 5. Architecture `translator.launch.py` starts two long-lived processes: 1. `migrate_ws/runtime/bin/mtranserver`, which owns Bergamot WASM and the `en_zh-Hans` model. 2. `mtran_bridge_node`, which exposes the ROS2 service and converts service requests into local HTTP requests. The C++ bridge communicates only with `http://127.0.0.1:8989`. It does not expose MTranServer's language, HTML, batch, or authentication features through ROS2. The MTranServer executable remains a separate Bun build product. CMake builds only the ROS2 C++ package. The build script stages a native MTranServer binary at `migrate_ws/runtime/bin/mtranserver`; it does not create an architecture-specific ROS2 source tree. ## 6. ROS2 Interface The service type is `mtran_ros2/srv/TranslateEnglishToChinese`: ```srv string text --- bool success string translation string error ``` The service name is fixed: ```text /translate_en_to_zh ``` Successful response: ```yaml success: true translation: "..." error: "" ``` Failed response: ```yaml success: false translation: "" error: "ERROR_CODE" ``` ## 7. Request Processing The node uses `rclcpp::executors::SingleThreadedExecutor`. It performs at most one translation at a time and does not create an application-level request queue or worker pool. For every valid request, the bridge sends: ```json { "from": "en", "to": "zh-Hans", "text": "request text", "html": false } ``` to `POST /translate`. A successful upstream response must be HTTP 200 with this shape: ```json { "result": "translated text" } ``` The bridge returns the result only after the HTTP request completes. It does not retry a translation request. ## 8. Input Constraints Input validation occurs before HTTP communication: - Empty or whitespace-only input returns `INVALID_INPUT`. - Input longer than 512 characters returns `INPUT_TOO_LONG`. - Valid input is treated as plain English text. The 512-character limit keeps the existing MTranServer call on its direct fixed-language path and avoids its long-text language-detection and segmentation path. ## 9. Startup and Model Residency Launch supplies these MTranServer settings: ```text MT_HOST=127.0.0.1 MT_PORT=8989 MT_ENABLE_UI=false MT_OFFLINE=true MT_CHECK_UPDATE=false MT_MODEL_DIR= MT_WORKER_IDLE_TIMEOUT=86400 MT_CACHE_SIZE=100 ``` The bridge does not advertise `/translate_en_to_zh` until it has: 1. Received a successful response from `GET /health`. 2. Completed one internal `en -> zh-Hans` warm-up translation. The existing MTranServer treats an idle timeout of zero as an immediate timeout, so this migration does not set it to zero and does not modify code outside `migrate_ws`. Instead, the bridge sends one internal warm-up translation every 43,200 seconds. Normal client requests also reset MTranServer's idle timer. The 86,400-second timeout and 43,200-second keep-warm interval keep the model resident without repeatedly starting the process. Launch terminates both processes together. If MTranServer exits unexpectedly, launch respawns it. During the outage, service calls fail with `NOT_READY`. After health recovers, the bridge warms the model before returning to ready state. ## 10. Configuration `translator.yaml` defines: ```yaml mtran_bridge_node: ros__parameters: server_url: "http://127.0.0.1:8989" request_timeout_ms: 30000 startup_timeout_ms: 60000 health_retry_ms: 500 keep_warm_interval_s: 43200 max_input_characters: 512 ``` Language direction and HTML mode are compile-time behavior, not ROS parameters. Launch arguments allow overriding the MTranServer executable path, model directory, offline records/configuration directory, parameter file, host, and port. Defaults point to the corresponding paths under `migrate_ws` for local development. ## 11. Error Semantics The service returns these exact error codes: | Code | Meaning | | --- | --- | | `INVALID_INPUT` | Input is empty or only whitespace. | | `INPUT_TOO_LONG` | Input exceeds 512 characters. | | `NOT_READY` | MTranServer is starting, unavailable, or being rewarmed. | | `TIMEOUT` | The translation request exceeded 30 seconds. | | `UPSTREAM_ERROR` | MTranServer returned a non-200 HTTP response or transport error. | | `INVALID_RESPONSE` | The upstream body is not JSON containing a string `result`. | Errors are logged without logging complete request text. Failed requests always return an empty `translation`. The original English input is never substituted as a successful result. ## 12. Component Boundaries ### `HttpTranslationClient` Owns libcurl setup, HTTP calls, timeout handling, request JSON serialization, and response JSON parsing. It exposes health checking and fixed-direction translation methods without any ROS2 dependency. ### `TranslatorNode` Owns ROS parameters, readiness state, input validation, service creation, startup warm-up, periodic keep-warm, and mapping client failures to service responses. It depends on the translation client through an interface so tests can inject deterministic results. ### `main.cpp` Initializes ROS2, creates `TranslatorNode`, and spins it in a `SingleThreadedExecutor`. ### `translator.launch.py` Resolves launch arguments, starts the MTranServer sidecar with the fixed environment, enables respawn, and starts the bridge node. ## 13. Testing Automated tests cover: - Empty and whitespace-only request rejection - 513-character request rejection - Boundary acceptance at 512 characters - Correct fixed HTTP request payload - Successful HTTP response parsing - Non-200 response mapping - Timeout mapping - Invalid JSON and missing/non-string `result` mapping - Service success and failure response fields - Startup readiness transition after health and warm-up - Recovery transition after a simulated sidecar outage - Keep-warm scheduling using short test-only intervals HTTP client tests use a local mock HTTP endpoint and require no model or network. Node tests inject a fake translation client. A manual smoke test uses the native staged MTranServer binary and the local `en_zh-Hans` model: ```bash ros2 service call /translate_en_to_zh \ mtran_ros2/srv/TranslateEnglishToChinese \ "{text: 'The robot has completed the inspection.'}" ``` The expected response has `success: true`, a non-empty Chinese `translation`, and an empty `error`. ## 14. Build and Deployment The local workflow is: 1. Build the current machine's MTranServer executable into `migrate_ws/runtime/bin/mtranserver`. 2. Place the `en_zh-Hans` model in `migrate_ws/models/en_zh-Hans`. 3. Run `colcon build` from `migrate_ws`. 4. Source `migrate_ws/install/setup.bash`. 5. Start `translator.launch.py`. 6. Run the service smoke test. The README also documents ARM64 deployment. On ARM64, the same C++ source is built with the target's native `colcon`; the sidecar is built with Bun's `bun-linux-arm64` target or built natively on the ARM64 device. ## 15. Acceptance Criteria - Every new migration file is under `migrate_ws`. - `colcon build --packages-select mtran_ros2` succeeds on ROS2 Humble and Ubuntu 22.04 when declared dependencies are installed. - Launch starts both the native MTranServer sidecar and the C++ bridge. - The ROS2 service appears only after health checking and model warm-up succeed. - A valid English sentence of at most 512 characters returns Simplified Chinese in the service response. - Empty, oversized, unavailable, timeout, upstream, and malformed-response cases return the specified error code and an empty translation. - Translation work is serialized on one executor thread. - The model remains warm during idle operation through the keep-warm mechanism. - Runtime translation works without network access after the executable and model are staged. - README includes local native build instructions and separate ARM64 build/deployment instructions.