diff --git a/ConfigurationDeskMCP/sources/models/hardware_inputs.py b/ConfigurationDeskMCP/sources/models/hardware_inputs.py index 57c1c01..65d16e9 100644 --- a/ConfigurationDeskMCP/sources/models/hardware_inputs.py +++ b/ConfigurationDeskMCP/sources/models/hardware_inputs.py @@ -8,11 +8,11 @@ class AddHardwarePlatformInput(BaseModel): ip_addresses: List[str] = Field( - description="Address(es) of the platform to register, e.g. ['192.0.2.10']. VEOS is NOT a platform - do not use this for VEOS." + description="Address(es) of the platform to register, e.g. ['192.0.2.10']. VEOS is not a real-time hardware platform - do not use this for VEOS." ) platform_type: str = Field( default="SCALEXIO", - description="Platform type: 'SCALEXIO', 'MicroAutoBox III', or 'MicroLabBox II'. VEOS is NOT a platform - use add_application_processing_unit / generate_bus_containers instead.", + description="Platform type: 'SCALEXIO', 'MicroAutoBox III', or 'MicroLabBox II'. VEOS is not a real-time hardware platform - use add_processing_unit_application / generate_bus_containers instead.", ) diff --git a/ConfigurationDeskMCP/sources/prompts/configurationdesk_prompts.py b/ConfigurationDeskMCP/sources/prompts/configurationdesk_prompts.py index 519b471..0a87891 100644 --- a/ConfigurationDeskMCP/sources/prompts/configurationdesk_prompts.py +++ b/ConfigurationDeskMCP/sources/prompts/configurationdesk_prompts.py @@ -70,10 +70,21 @@ def bus_manager_restbus_simulation( For .slx/.mdl also call `analyze_models`; .sic/.bsc are already analyzed. Then `add_model_to_signal_chain` to expose the model ports. -## Step 6 — Provide scheduling -A processing unit must exist. For real hardware, register it with -`add_hardware_platform`; for VEOS/offline, call `add_application_processing_unit`. -Then `create_application_process` (auto-assigned to "{bus_config_name}"). +## Step 6 — Create the application process and assign the model +When creating an application process, a Processing Unit Application should be +automatically created if one does not already exist. If exactly one Processing Unit +Application exists, the Application Process should be added to it. + +Assign the behavior model from Step 5 by calling +`create_preconfigured_application_process` with the model's name (use `list_models` to +get the exact name of "{model_path}"). This creates an application process pre-configured +for that model — its runnable function is placed in the process's default task. + +Then assign the bus configuration to that application process with +`assign_bus_config_to_application_process` (bus_config_name="{bus_config_name}"). + +For a model-less restbus process instead, call `create_application_process` — it +auto-assigns to "{bus_config_name}". ## Step 7 — Connect the ports Call `auto_connect_matching_io_function_blocks_to_model_ports` to wire matching diff --git a/ConfigurationDeskMCP/sources/prompts/individual_setup_prompts.py b/ConfigurationDeskMCP/sources/prompts/individual_setup_prompts.py index 9cdd2ab..9b8517b 100644 --- a/ConfigurationDeskMCP/sources/prompts/individual_setup_prompts.py +++ b/ConfigurationDeskMCP/sources/prompts/individual_setup_prompts.py @@ -257,8 +257,8 @@ def create_application_process( {_ENSURE_RUNNING} ## Prerequisite -A ProcessingUnitApplication must exist: register a hardware platform -(`add_hardware_platform`) or, for VEOS/no-hardware, call `add_application_processing_unit`. +A ProcessingUnitApplication is mandatory regardless of the target (real-time +hardware or BSC/VEOS). If none exists yet, call `add_processing_unit_application`. ## Step 1 — Create the process (default periodic task) Call `create_application_process` with name="{process_name}". This sets @@ -386,7 +386,7 @@ def build_application( @mcp.prompt( name="register_hardware", - description="Single task: provide the hardware topology — register a SCALEXIO/MicroAutoBox III/MicroLabBox II platform, import an .htfx file, or add a VEOS processing unit", + description="Single task: provide the hardware topology — register and scan a SCALEXIO/MicroAutoBox III/MicroLabBox II platform or import an .htfx file for a real-time application. A non-empty hardware topology is NOT necessary to generate bus simulation containers (BSC)", ) def register_hardware( platform_type: str = "SCALEXIO", @@ -396,23 +396,30 @@ def register_hardware( # Register Hardware Goal: provide the hardware topology that bus and I/O function blocks are assigned to. +NOTE: a hardware topology is required to build and download a real-time application +for SCALEXIO, MicroAutoBox III, or MicroLabBox II. It describes the components of a +specific hardware system, such as channel types and slot numbers. + +GENERAL REMARK: a non-empty hardware topology — created for example by registering +and scanning hardware — is NOT necessary to generate bus simulation containers (BSC). +When BSC output is the only deliverable, skip this prompt entirely and use +`generate_bus_containers`. Working without a hardware topology means bus +configurations are not assigned to application processes via real-time hardware +access, so assign them manually instead. {_ENSURE_RUNNING} ## Choose an approach (ASK the user which applies) -### Option A — Physical platform +### Option A — Register and scan a physical platform Call `add_hardware_platform` with ip_addresses=["{platform_ip}"], platform_type="{platform_type}". Valid types: "SCALEXIO", "MicroAutoBox III", "MicroLabBox II". -Returns the unique platform name used by later hardware operations. +Returns the unique platform name used by later hardware operations. Scanning the +registered platform creates a matching hardware topology. ### Option B — Import a topology file Call `import_hardware_topology` with path="C:/HW/topology.htfx". -### Option C — VEOS / no hardware -Call `add_application_processing_unit`. VEOS is NOT a platform — do NOT call -`add_hardware_platform` for it. The deliverable is the generated BSC. - ## Verify / maintain - `list_platforms` — confirm the registered hardware and its I/O boards. - `refresh_platforms` — refresh platform information. @@ -477,7 +484,7 @@ def assign_bus_hardware( ## Prerequisite - A bus configuration with assigned ECUs (see `create_bus_configuration`). - Hardware present: `add_hardware_platform` / `import_hardware_topology`, or - `add_application_processing_unit` for VEOS (then skip channel assignment). + `add_processing_unit_application` for VEOS (then skip channel assignment). ## Step 1 — Inspect what needs hardware Call `list_bus_access_requests` — each cluster/part generates one request to assign. @@ -571,7 +578,7 @@ def inspect_configuration() -> str: Call `get_application_status` — project name, project root, and active application. ## Configuration tree -Call `list_configuration` — executable applications, processing units, tasks, events. +Call `list_configuration` — executable applications, processing unit applications, application processes, tasks, events. ## Inventory - `list_applications` — applications in the project. diff --git a/ConfigurationDeskMCP/sources/resources/domain_resources.py b/ConfigurationDeskMCP/sources/resources/domain_resources.py index e50964a..d82eefd 100644 --- a/ConfigurationDeskMCP/sources/resources/domain_resources.py +++ b/ConfigurationDeskMCP/sources/resources/domain_resources.py @@ -91,7 +91,7 @@ ## Hardware Platforms SCALEXIO, MicroAutoBox III, and MicroLabBox II platforms are registered by address and scanned to create the hardware topology. Use `list_platforms` to see -registered hardware. VEOS is NOT a platform — use `add_application_processing_unit`. +registered hardware. VEOS is not a real-time hardware platform. """ _TOOL_CATEGORIES = """\ @@ -597,7 +597,7 @@ - `SCALEXIO` - `MicroAutoBox III` - `MicroLabBox II` -(VEOS is NOT a platform — use `add_application_processing_unit`.) +(VEOS is not a real-time hardware platform.) ## `bus_type` (create_io_function_block) - `CAN` diff --git a/ConfigurationDeskMCP/sources/server/app.py b/ConfigurationDeskMCP/sources/server/app.py index 4535ee2..fb5ff0d 100644 --- a/ConfigurationDeskMCP/sources/server/app.py +++ b/ConfigurationDeskMCP/sources/server/app.py @@ -104,8 +104,8 @@ async def _lifespan(server: FastMCP) -> Any: # type: ignore[type-arg] 13. Hardware topology → ASK USER which approach: - `add_hardware_platform` → register SCALEXIO, MicroAutoBox III, or MicroLabBox II hardware (needs address from user) - `import_hardware_topology` → import .htfx file (needs file path from user) - - `add_application_processing_unit` → no physical hardware / VEOS workflow - - VEOS does NOT need platform registration. Use generate_bus_containers for BSC files. + - `add_processing_unit_application` → no physical hardware / VEOS workflow + - VEOS is not a real-time hardware platform; use generate_bus_containers to produce the BSC files it consumes. 14. `create_io_function_block` → create CAN/LIN/Ethernet I/O block 15. `set_io_function_block_property` → set BaudRate 16. `assign_bus_access` → link bus access requests to function block diff --git a/ConfigurationDeskMCP/sources/services/app_management_service.py b/ConfigurationDeskMCP/sources/services/app_management_service.py index 0c4ce44..ca42a0a 100644 --- a/ConfigurationDeskMCP/sources/services/app_management_service.py +++ b/ConfigurationDeskMCP/sources/services/app_management_service.py @@ -81,3 +81,36 @@ async def list_applications() -> str: except Exception as e: logger.exception("Error listing applications") return error_response(str(e), transient=False) + + +async def add_processing_unit_application() -> str: + """Add a processing unit application to the executable application. + + An executable application has one or more processing unit applications (PUA); + each PUA is executed on one processing unit (PU) and hosts one or more + application processes (AP). Add one explicitly when no registered hardware or + imported topology already provides one — typically a no-hardware or VEOS build. + """ + try: + conn = await _get_live_connection() + result = await dispatch(app_management_com.add_processing_unit_application, conn) + pua_created = result.get("processing_unit_application_created", False) + if not pua_created: + return error_response( + f"ProcessingUnitApplication could not be added: " + f"{result.get('processing_unit_application_detail', '')}. " + "You may need to add it manually in ConfigurationDesk.", + transient=False, + ) + return success_response( + message=( + "ProcessingUnitApplication added. " + "For VEOS: use generate_bus_containers to produce BSC files." + ), + verified=True, + ) + except BridgeError as exc: + return tool_error_result(exc) + except Exception as e: + logger.exception("Error adding processing unit application") + return error_response(str(e), transient=False) diff --git a/ConfigurationDeskMCP/sources/services/bus_access_service.py b/ConfigurationDeskMCP/sources/services/bus_access_service.py index 0f497c6..1b3db83 100644 --- a/ConfigurationDeskMCP/sources/services/bus_access_service.py +++ b/ConfigurationDeskMCP/sources/services/bus_access_service.py @@ -272,7 +272,7 @@ async def auto_assign_channel_set(function_block_name: str, bus_type: str = "CAN f"Function block '{function_block_name}' not found. " f"Verify it exists with list_io_function_block_properties. " f"If the block is LIN, pass bus_type='LIN'. " - f"Also ensure a hardware topology exists via add_application_processing_unit or add_hardware_platform." + f"Also ensure a hardware topology exists (add_hardware_platform or import_hardware_topology), or add a processing unit application (add_processing_unit_application) for a no-hardware build." ), ) return error_response(detail, transient=False) @@ -390,7 +390,7 @@ async def create_preconfigured_application_process(model_name: str) -> str: next_action=( "Pre-configured application process creation failed. " "Verify the model exists in the topology and a ProcessingUnitApplication " - "is available (registered hardware or `add_application_processing_unit`). " + "is available (registered hardware or `add_processing_unit_application`). " "Do NOT retry with the same parameters." ), ) @@ -406,7 +406,7 @@ async def create_preconfigured_application_process(model_name: str) -> str: "No new application process became observable after the pre-configured creation call.", transient=False, next_action=( - "Verify a ProcessingUnitApplication exists (use `add_application_processing_unit` " + "Verify a ProcessingUnitApplication exists (use `add_processing_unit_application` " "for VEOS workflows, or register hardware). Then call `create_application_process` " "as the manual fallback." ), diff --git a/ConfigurationDeskMCP/sources/services/hardware_service.py b/ConfigurationDeskMCP/sources/services/hardware_service.py index 5d8df38..4063447 100644 --- a/ConfigurationDeskMCP/sources/services/hardware_service.py +++ b/ConfigurationDeskMCP/sources/services/hardware_service.py @@ -27,7 +27,7 @@ async def add_hardware_platform(ip_addresses: list[str], platform_type: str = "S transient=False, retryable=False, next_action=( - "VEOS is not a hardware platform. For VEOS workflows: " + "VEOS is not a real-time hardware platform and cannot be registered in ConfigurationDesk. For VEOS workflows: " "1) Use generate_bus_containers to create BSC files, " "2) Import BSC files into VEOS. " "For SCALEXIO: ensure hardware is powered on and reachable." @@ -160,32 +160,3 @@ async def add_hardware_element(element_type: str) -> str: except Exception as e: logger.exception("Error adding hardware element") return error_response(str(e), transient=False) - - -async def add_application_processing_unit() -> str: - """Add a ProcessingUnitApplication to the application configuration. - - Used for VEOS-targeted or no-hardware workflows. - """ - try: - conn = get_connection() - result = await dispatch(hardware_com.add_application_processing_unit, conn) - pu_created = result.get("processing_unit_created", False) - if not pu_created: - return error_response( - f"ProcessingUnitApplication could not be added: {result.get('processing_unit_detail', '')}. " - "You may need to add it manually in ConfigurationDesk.", - transient=False, - ) - return success_response( - message=( - "ProcessingUnitApplication added. " - "For VEOS: use generate_bus_containers to produce BSC files." - ), - verified=True, - ) - except BridgeError as exc: - return tool_error_result(exc) - except Exception as e: - logger.exception("Error adding application processing unit") - return error_response(str(e), transient=False) diff --git a/ConfigurationDeskMCP/sources/services/model_topology_service.py b/ConfigurationDeskMCP/sources/services/model_topology_service.py index 6dc31ba..cdecf4b 100644 --- a/ConfigurationDeskMCP/sources/services/model_topology_service.py +++ b/ConfigurationDeskMCP/sources/services/model_topology_service.py @@ -148,7 +148,7 @@ async def create_application_process( "No new application process became observable after the creation call.", transient=False, next_action=( - "Verify a ProcessingUnitApplication exists (use `add_application_processing_unit` " + "Verify a ProcessingUnitApplication exists (use `add_processing_unit_application` " "for VEOS workflows or register a hardware platform), then retry." ), ) diff --git a/ConfigurationDeskMCP/sources/tools/app_management.py b/ConfigurationDeskMCP/sources/tools/app_management.py index f46deef..c44fbc6 100644 --- a/ConfigurationDeskMCP/sources/tools/app_management.py +++ b/ConfigurationDeskMCP/sources/tools/app_management.py @@ -7,6 +7,7 @@ RemoveApplicationInput, ) from sources.server.app import mcp +from sources.server.preconditions import with_preconditions from sources.services import app_management_service as svc @@ -69,3 +70,26 @@ async def remove_application(input: RemoveApplicationInput) -> str: ) async def list_applications() -> str: return await svc.list_applications() + + +@mcp.tool( + name="add_processing_unit_application", + description=( + "Add a processing unit application to the executable application. " + "An executable application has one or more processing unit applications (PUA); " + "each PUA is executed on one processing unit (PU) and hosts one or more " + "application processes (AP). Add one explicitly when no " + "registered hardware or imported topology already provides one — typically a " + "no-hardware or VEOS build. VEOS is not a real-time hardware platform; " + "it consumes generated Bus Simulation Containers (BSC)." + ), + annotations={ + "readOnlyHint": False, + "destructiveHint": False, + "idempotentHint": True, + "openWorldHint": False, + }, +) +@with_preconditions("connection", "project", "application") +async def add_processing_unit_application() -> str: + return await svc.add_processing_unit_application() diff --git a/ConfigurationDeskMCP/sources/tools/bus_access.py b/ConfigurationDeskMCP/sources/tools/bus_access.py index 4e6cdd1..3423b7a 100644 --- a/ConfigurationDeskMCP/sources/tools/bus_access.py +++ b/ConfigurationDeskMCP/sources/tools/bus_access.py @@ -387,7 +387,7 @@ async def auto_connect_matching_io_function_blocks_to_model_ports() -> str: "Create a pre-configured application process for one specific model. " "Calls Algorithms.CreatePreConfiguredApplicationProcessAutomatically([model], None). " "A new ProcessingUnitApplication is created automatically when no Parent is supplied " - "(VEOS workflows: ensure `add_application_processing_unit` was called first). " + "(VEOS workflows: ensure `add_processing_unit_application` was called first). " "For all-models behavior, prefer create_application_process or pass `model_names` to it." ), annotations={ diff --git a/ConfigurationDeskMCP/sources/tools/configuration.py b/ConfigurationDeskMCP/sources/tools/configuration.py index 18fe38c..4fc6a10 100644 --- a/ConfigurationDeskMCP/sources/tools/configuration.py +++ b/ConfigurationDeskMCP/sources/tools/configuration.py @@ -14,7 +14,8 @@ name="list_configuration", description=( "List the application configuration tree " - + "(executable applications, processing units, tasks, events). " + + "(executable applications, processing unit applications, application processes, " + + "tasks, events). " + "Results are paginated; call again with next_offset to retrieve later pages." ), annotations={ diff --git a/ConfigurationDeskMCP/sources/tools/hardware.py b/ConfigurationDeskMCP/sources/tools/hardware.py index a54df21..d9a3799 100644 --- a/ConfigurationDeskMCP/sources/tools/hardware.py +++ b/ConfigurationDeskMCP/sources/tools/hardware.py @@ -16,8 +16,7 @@ "BEFORE calling this tool, ASK the user which hardware approach they want: " "1) Provide address of SCALEXIO, MicroAutoBox III, or MicroLabBox II hardware → use this tool, " "2) Import an .htfx topology file → use import_hardware_topology, " - "3) Create empty topology (VEOS/no hardware) → use add_application_processing_unit. " - "VEOS is NOT a platform - never call this for VEOS. " + "VEOS is not a real-time hardware platform - never call this for VEOS. " "Returns the unique platform name for subsequent hardware operations." ), annotations={ @@ -38,7 +37,7 @@ async def add_hardware_platform( platform_type: Annotated[ str, Field( - description="Platform type: 'SCALEXIO', 'MicroAutoBox III', or 'MicroLabBox II'. VEOS is not a platform.", + description="Platform type: 'SCALEXIO', 'MicroAutoBox III', or 'MicroLabBox II'. VEOS is not a real-time hardware platform.", ), ] = "SCALEXIO", ) -> str: @@ -167,24 +166,3 @@ async def add_hardware_element( ], ) -> str: return await svc.add_hardware_element(element_type) - - -@mcp.tool( - name="add_application_processing_unit", - description=( - "Add a ProcessingUnitApplication to the application configuration. " - "Use this for VEOS-targeted or no-hardware workflows where the project needs " - "an application processing unit to host I/O function blocks and application processes. " - "VEOS does NOT use registered hardware platforms; it consumes generated Bus " - "Simulation Containers (BSC)." - ), - annotations={ - "readOnlyHint": False, - "destructiveHint": False, - "idempotentHint": True, - "openWorldHint": False, - }, -) -@with_preconditions("connection", "project", "application") -async def add_application_processing_unit() -> str: - return await svc.add_application_processing_unit() diff --git a/ConfigurationDeskMCP/sources/tools/model_topology.py b/ConfigurationDeskMCP/sources/tools/model_topology.py index 492f6c3..3b4794d 100644 --- a/ConfigurationDeskMCP/sources/tools/model_topology.py +++ b/ConfigurationDeskMCP/sources/tools/model_topology.py @@ -112,7 +112,7 @@ async def analyze_models() -> str: "Pass `bus_config_names` to scope the assignment to specific configurations, or pass an " "empty list `[]` to skip assignment entirely. " "PRECONDITION: a ProcessingUnitApplication must exist (register a hardware platform or call " - "`add_application_processing_unit` for VEOS workflows)." + "`add_processing_unit_application` for VEOS workflows)." ), annotations={ "readOnlyHint": False, diff --git a/ConfigurationDeskMCP/tests/_fake_bridge.py b/ConfigurationDeskMCP/tests/_fake_bridge.py index afaf0fe..0274bd3 100644 --- a/ConfigurationDeskMCP/tests/_fake_bridge.py +++ b/ConfigurationDeskMCP/tests/_fake_bridge.py @@ -201,9 +201,9 @@ def _create_preconfigured_application_process(model_name, *args): "hardware_items": [etype or "Element"], "verified": True, }, - "add_application_processing_unit": { - "processing_unit_created": True, - "processing_unit_detail": "created", + "add_processing_unit_application": { + "processing_unit_application_created": True, + "processing_unit_application_detail": "created", }, "import_hardware_topology": {"verified": True, "hardware_items": ["SCALEXIO_Rack"]}, "scan_hardware": {"verified": True, "hardware_items": ["SCALEXIO_Rack"]}, diff --git a/ConfigurationDeskMCP/tests/domains/test_app_management.py b/ConfigurationDeskMCP/tests/domains/test_app_management.py index 94e0a26..4e10088 100644 --- a/ConfigurationDeskMCP/tests/domains/test_app_management.py +++ b/ConfigurationDeskMCP/tests/domains/test_app_management.py @@ -10,6 +10,7 @@ "activate_application", "remove_application", "list_applications", + "add_processing_unit_application", ) @@ -28,3 +29,7 @@ def test_remove_application(fake_bridge): def test_list_applications(fake_bridge): run_ok(appmgmt_svc.list_applications()) + + +def test_add_processing_unit_application(fake_bridge): + run_ok(appmgmt_svc.add_processing_unit_application()) diff --git a/ConfigurationDeskMCP/tests/domains/test_hardware.py b/ConfigurationDeskMCP/tests/domains/test_hardware.py index 08de93d..e1a4c77 100644 --- a/ConfigurationDeskMCP/tests/domains/test_hardware.py +++ b/ConfigurationDeskMCP/tests/domains/test_hardware.py @@ -8,7 +8,6 @@ COVERS = ( "add_hardware_platform", "add_hardware_element", - "add_application_processing_unit", "import_hardware_topology", "scan_hardware", "remove_hardware", @@ -27,10 +26,6 @@ def test_add_hardware_element(fake_bridge): assert payload["element_name"] == "DS1513" -def test_add_application_processing_unit(fake_bridge): - run_ok(hw_svc.add_application_processing_unit()) - - def test_import_hardware_topology(fake_bridge): run_ok(hw_svc.import_hardware_topology("D:/topology.htfx")) diff --git a/ConfigurationDeskMCP/tests/domains/test_model_topology.py b/ConfigurationDeskMCP/tests/domains/test_model_topology.py index a440627..ff4cfee 100644 --- a/ConfigurationDeskMCP/tests/domains/test_model_topology.py +++ b/ConfigurationDeskMCP/tests/domains/test_model_topology.py @@ -98,12 +98,12 @@ def GetElements(self, parent): return _FakeCollection([processing_unit]) return _FakeCollection([]) - pu_application, detail = model_topology_com._resolve_processing_unit_application( + pua, detail = model_topology_com._resolve_processing_unit_application( SimpleNamespace(), _FakeRelation(), ) - assert pu_application is processing_unit + assert pua is processing_unit assert detail == "" @@ -117,12 +117,12 @@ def GetTopNodes(self): def GetElements(self, _parent): return _FakeCollection([]) - pu_application, detail = model_topology_com._resolve_processing_unit_application( + pua, detail = model_topology_com._resolve_processing_unit_application( SimpleNamespace(), _FakeRelation(), ) - assert pu_application is None + assert pua is None assert ( "No ProcessingUnitApplication is available under the active executable application" in detail diff --git a/ConfigurationDeskMCP/tests/mcp_inventory.json b/ConfigurationDeskMCP/tests/mcp_inventory.json index 0948042..6a8a99c 100644 --- a/ConfigurationDeskMCP/tests/mcp_inventory.json +++ b/ConfigurationDeskMCP/tests/mcp_inventory.json @@ -2,7 +2,6 @@ "tools": [ "activate_application", "add_application", - "add_application_processing_unit", "add_communication_matrix", "add_feature_to_bus_element", "add_hardware_element", @@ -11,6 +10,7 @@ "add_model", "add_model_port_to_signal_chain", "add_model_to_signal_chain", + "add_processing_unit_application", "analyze_models", "assign_bus_access", "assign_bus_config_to_application_process", diff --git a/ConfigurationDeskMCP/tests/test_small_model_host_profile.py b/ConfigurationDeskMCP/tests/test_small_model_host_profile.py index 57be6c4..c4617b1 100644 --- a/ConfigurationDeskMCP/tests/test_small_model_host_profile.py +++ b/ConfigurationDeskMCP/tests/test_small_model_host_profile.py @@ -39,7 +39,7 @@ "`set_io_function_block_property`", "`add_hardware_platform`", "`import_hardware_topology`", - "`add_application_processing_unit`", + "`add_processing_unit_application`", "`add_model_to_signal_chain`", "`add_model_port_to_signal_chain`", "`create_preconfigured_application_process`", diff --git a/configurationdesk_com_bridge/domains/app_management_com.py b/configurationdesk_com_bridge/domains/app_management_com.py index 71e4be3..0df5d67 100644 --- a/configurationdesk_com_bridge/domains/app_management_com.py +++ b/configurationdesk_com_bridge/domains/app_management_com.py @@ -102,3 +102,67 @@ def list_applications(connection) -> dict[str, Any]: except Exception: pass return {"applications": apps, "active": active} + + +def _create_processing_unit_application(connection) -> tuple[bool, str]: + """Create a ProcessingUnitApplication child under the top ApplicationConfiguration node. + + Returns (created, detail). ``created=False`` means the call did not succeed + but was not fatal. + """ + try: + atm_relation = connection.relations.Item("ApplicationConfiguration") + except Exception as exc: + return False, f"ApplicationConfiguration relation not available: {exc}" + + try: + top_nodes = atm_relation.GetTopNodes() + if top_nodes.Count == 0: + return False, "ApplicationConfiguration has no top-level executable application" + exec_application = top_nodes.Item(0) + except Exception as exc: + return False, f"Cannot read executable application: {exc}" + + # Preferred path: GetCreatableTypes + CreateDataObject (matches COM examples). + try: + creatable = atm_relation.GetCreatableTypes(exec_application) + target_type = None + for idx in range(1, creatable.Count + 1): + cand = creatable.Item(idx) + try: + cand_name = cand.Name + except Exception: + continue + if cand_name == "ProcessingUnitApplication": + target_type = cand + break + if target_type is None and creatable.Count > 0: + target_type = creatable.Item(1) + if target_type is not None: + atm_relation.CreateDataObject(target_type, exec_application) + return True, "Created via GetCreatableTypes/CreateDataObject" + except Exception as exc: + _log.debug("CreateDataObject path failed: %s", exc) + + # Fallback: CreateChild on the executable application using DataObjectTypes. + try: + type_obj = exec_application.DataObjectTypes.Item("ProcessingUnitApplication") + exec_application.CreateChild(type_obj) + return True, "Created via CreateChild" + except Exception as exc: + return False, f"Cannot create ProcessingUnitApplication: {exc}" + + +def add_processing_unit_application(connection) -> dict[str, Any]: + """Add a processing unit application to the executable application. + + An executable application has one or more processing unit applications (PUA); + each PUA is executed on one processing unit (PU) and hosts one or more + application processes (AP). This adds one explicitly under the top-level + ApplicationConfiguration node. + """ + pua_created, pua_detail = _create_processing_unit_application(connection) + return { + "processing_unit_application_created": pua_created, + "processing_unit_application_detail": pua_detail, + } diff --git a/configurationdesk_com_bridge/domains/hardware_com.py b/configurationdesk_com_bridge/domains/hardware_com.py index 5251c2d..828726d 100644 --- a/configurationdesk_com_bridge/domains/hardware_com.py +++ b/configurationdesk_com_bridge/domains/hardware_com.py @@ -3,15 +3,23 @@ All functions must be called on the STA thread via dispatch(). IMPORTANT CONCEPTS: -- Hardware platforms (SCALEXIO, MicroAutoBox III, MicroLabBox II) are registered via - PlatformManagement.RegisterPlatform and then scanned to create a hardware topology. -- VEOS is NOT a platform. VEOS uses Bus Simulation Containers (BSC) generated by - BusManager.Configure("GenerateContainers", []). BSCs are then imported into VEOS - to build an offline simulation application. -- Hardware topology can be created in three ways: - Mode 0: Scan registered hardware platform +In ConfigurationDesk a platform is a dSPACE real-time hardware system. +Hardware platforms (SCALEXIO, MicroAutoBox III, MicroLabBox II) can be registered via +PlatformManagement.RegisterPlatform. + +VEOS is a PC-based simulation platform, not a real-time hardware platform. For building offline +simulation applications, the dSPACE product VEOS is necessary. Bus Simulation Containers (BSCs) that +are generated with the Bus Manager via BusManager.Configure("GenerateContainers", []) can either +be used in ConfigurationDesk applications to create a real-time application or can be imported to +VEOS to perform SIL simulation. + +A hardware topology is a component of a ConfigurationDesk application that contains information on +specific hardware systems. A hardware topology can be created in three ways: + Mode 0: Scan registered platform Mode 1: Import .htfx file - Mode 2: Create empty topology (for VEOS or no-hardware scenarios) + Mode 2: Create hardware topology from scratch. + +For the generation of a BSC, an empty hardware topology is sufficient. """ from __future__ import annotations @@ -146,20 +154,18 @@ def add_hardware_platform( ) -> dict[str, Any]: """Register and scan a SCALEXIO hardware platform by IP address(es). - VEOS is NOT a platform. For VEOS workflows: + VEOS is not a real-time hardware platform and cannot be registered in ConfigurationDesk. For VEOS workflows: - Use generate_bus_containers to create BSC files - - Import BSC files into VEOS separately - - Or use add_application_processing_unit for an empty topology (no download needed) + - Import the BSC files into VEOS, which builds the offline simulation application """ if platform_type.upper() == "VEOS": return { "error": True, "detail": ( - "VEOS is not a hardware platform and cannot be registered. " + "VEOS is not a real-time hardware platform and cannot be registered here. " "For VEOS workflows: 1) Configure your bus configuration normally, " "2) Call generate_bus_containers to generate BSC files, " - "3) Import the BSC files into VEOS to build an offline simulation application. " - "If you need a hardware topology for the build, use add_application_processing_unit." + "3) Import the BSC files into VEOS, which builds the offline simulation application. " ), } @@ -257,70 +263,6 @@ def add_hardware_platform( } -def _create_processing_unit_application(connection) -> tuple[bool, str]: - """Create a ProcessingUnitApplication child under the top ApplicationConfiguration node. - - Returns (created, detail). ``created=False`` means the call did not succeed - but was not fatal — the empty topology was still created. - """ - try: - atm_relation = connection.relations.Item("ApplicationConfiguration") - except Exception as exc: - return False, f"ApplicationConfiguration relation not available: {exc}" - - try: - top_nodes = atm_relation.GetTopNodes() - if top_nodes.Count == 0: - return False, "ApplicationConfiguration has no top-level execution application" - exec_application = top_nodes.Item(0) - except Exception as exc: - return False, f"Cannot read execution application: {exc}" - - # Preferred path: GetCreatableTypes + CreateDataObject (matches COM examples). - try: - creatable = atm_relation.GetCreatableTypes(exec_application) - target_type = None - for idx in range(1, creatable.Count + 1): - cand = creatable.Item(idx) - try: - cand_name = cand.Name - except Exception: - continue - if cand_name == "ProcessingUnitApplication": - target_type = cand - break - if target_type is None and creatable.Count > 0: - target_type = creatable.Item(1) - if target_type is not None: - atm_relation.CreateDataObject(target_type, exec_application) - return True, "Created via GetCreatableTypes/CreateDataObject" - except Exception as exc: - _log.debug("CreateDataObject path failed: %s", exc) - - # Fallback: CreateChild on the execution application using DataObjectTypes. - try: - type_obj = exec_application.DataObjectTypes.Item("ProcessingUnitApplication") - exec_application.CreateChild(type_obj) - return True, "Created via CreateChild" - except Exception as exc: - return False, f"Cannot create ProcessingUnitApplication: {exc}" - - -def add_application_processing_unit(connection) -> dict[str, Any]: - """Add a ProcessingUnitApplication to the application configuration. - - Used for VEOS-targeted workflows or applications with no physical hardware. - A ProcessingUnitApplication is added under the top-level - ApplicationConfiguration node so that I/O function blocks and application - processes can later be hosted by it. - """ - pu_created, pu_detail = _create_processing_unit_application(connection) - return { - "processing_unit_created": pu_created, - "processing_unit_detail": pu_detail, - } - - def import_hardware_topology(connection, path: str) -> dict[str, Any]: """Import hardware topology from an HTFX file.""" abs_path = os.path.abspath(path) diff --git a/configurationdesk_com_bridge/domains/model_topology_com.py b/configurationdesk_com_bridge/domains/model_topology_com.py index 2d50ea9..387ec4f 100644 --- a/configurationdesk_com_bridge/domains/model_topology_com.py +++ b/configurationdesk_com_bridge/domains/model_topology_com.py @@ -137,8 +137,9 @@ def _iter_relation_descendants(rel, parent): def _resolve_processing_unit_application(connection, atm_relation): """Locate the ProcessingUnitApplication under the executable application. - Returns ``(pu_application, error_detail)``. When the PU cannot be found, - ``pu_application`` is ``None`` and ``error_detail`` describes the cause. + Returns ``(pua, error_detail)``. When the processing unit application + cannot be found, ``pua`` is ``None`` and ``error_detail`` describes the + cause. """ try: top_nodes = atm_relation.GetTopNodes() @@ -146,10 +147,7 @@ def _resolve_processing_unit_application(connection, atm_relation): return None, f"Cannot read ApplicationConfiguration top nodes: {exc}" try: if top_nodes.Count == 0: - return None, ( - "ApplicationConfiguration has no top-level executable application. " - "Register a hardware platform or call add_application_processing_unit first." - ) + return None, "ApplicationConfiguration has no top-level executable application." exec_app = top_nodes.Item(0) except Exception as exc: return None, f"Cannot access executable application: {exc}" @@ -159,24 +157,24 @@ def _resolve_processing_unit_application(connection, atm_relation): except Exception as exc: return None, f"Cannot enumerate ApplicationConfiguration descendants: {exc}" - pu_application = None + pua = None for el in descendants: try: roles = list(el.Roles) except Exception: roles = [] if "ProcessingUnitApplication" in roles: - pu_application = el + pua = el break - if pu_application is None: + if pua is None: return None, ( "No ProcessingUnitApplication is available under the active executable application. " "ConfigurationDesk treats hardware topology and processing-unit applications as separate " "configuration objects; register/import hardware for processing-unit assignment, then add a " "ProcessingUnitApplication explicitly before creating an application process." ) - return pu_application, "" + return pua, "" def _set_provide_default_task(process) -> tuple[bool, str]: @@ -401,14 +399,14 @@ def create_application_process( except Exception as exc: return {"error": True, "detail": f"ApplicationConfiguration relation not available: {exc}"} - pu_application, detail = _resolve_processing_unit_application(connection, atm_relation) - if pu_application is None: + pua, detail = _resolve_processing_unit_application(connection, atm_relation) + if pua is None: return {"error": True, "detail": detail} before_processes = set(list_application_process_names(connection)) # 1) Create the application process. - process = _create_data_object(atm_relation, pu_application, "ApplicationProcess") + process = _create_data_object(atm_relation, pua, "ApplicationProcess") if process is None: return { "error": True, diff --git a/docs/prompts/tool-map.md b/docs/prompts/tool-map.md index e266e09..2a56a47 100644 --- a/docs/prompts/tool-map.md +++ b/docs/prompts/tool-map.md @@ -87,7 +87,7 @@ The table is checked by `ConfigurationDeskMCP/tests/test_prompt_tool_map.py` so | `list_platforms` | Hardware management | Prompt: `register_hardware` | List registered physical platforms before choosing one to rescan, modify, or remove. | | `refresh_platforms` | Hardware management | Prompt: `register_hardware` | Refresh information for registered platforms when their current status must be checked. | | `add_hardware_element` | Hardware management | [Hardware Management](../tools/hardware-management-mcp-tools.md) | Add the supported hardware element `DS6311` to the active hardware topology only when the required element type is known. | -| `add_application_processing_unit` | Hardware management | Prompt: `register_hardware` | For a VEOS or no-hardware workflow, add an application processing unit; do not register a physical platform. | +| `add_processing_unit_application` | Application management | [Application Management](../tools/app-management-mcp-tools.md) | For a VEOS or no-hardware workflow, add a processing unit application; do not register a physical platform. | | `create_io_function_block` | Bus access | Prompt: `assign_bus_hardware` | After creating `CAN_Restbus`, create physical CAN I/O function block `CAN_Body` for one bus channel. | | `set_io_function_block_property` | Bus access | Prompt: `assign_bus_hardware` | Set hardware I/O property `BaudRate=500000` on CAN function block `CAN_Body`; do not use a bus-feature property tool. | | `list_io_function_block_properties` | Bus access | Prompt: `assign_bus_hardware` | List the supported properties and current values of `CAN_Body` before setting an uncertain hardware I/O value. | diff --git a/docs/small-model-host-profile.md b/docs/small-model-host-profile.md index 21c97c0..4afe1a7 100644 --- a/docs/small-model-host-profile.md +++ b/docs/small-model-host-profile.md @@ -36,7 +36,7 @@ authoritative. hardware I/O value with `set_io_function_block_property`. - Use `add_hardware_platform` only for physical SCALEXIO, MicroAutoBox III, or MicroLabBox II hardware with an address. Use `import_hardware_topology` for - an `.htfx` file. Use `add_application_processing_unit` for VEOS or a + an `.htfx` file. Use `add_processing_unit_application` for VEOS or a no-hardware workflow. - Use `add_model_to_signal_chain` for every port of one model and `add_model_port_to_signal_chain` only for a named port. Use @@ -60,8 +60,8 @@ authoritative. - Use `list_bus_configurations` for top-level configuration names. Use `find_bus_config_elements` to inspect elements or exposed ports inside a selected configuration. -- Use `list_configuration` to inspect processing units, application processes, - tasks, and events before changing them. +- Use `list_configuration` to inspect processing unit applications, application + processes, tasks, and events before changing them. - Use `analyze_models` to discover model interfaces and ports. Use `check_conflicts` to validate the configured application before a build or hardware download; neither tool substitutes for the other. diff --git a/docs/tools/app-management-mcp-tools.md b/docs/tools/app-management-mcp-tools.md index f4a5be0..0da958e 100644 --- a/docs/tools/app-management-mcp-tools.md +++ b/docs/tools/app-management-mcp-tools.md @@ -13,6 +13,11 @@ for project-scoped model, matrix, bus, hardware, and build work. | `activate_application` | Make an existing application active. | Safe when the named application is already active. | | `remove_application` | Remove an application from the project. | Destructive; inspect current state first. | | `list_applications` | List applications and their active status. | Read-only. | +| `add_processing_unit_application` | Add a processing unit application (a component of the executable application that hosts application processes). | Requires an active application. | + +An executable application has one or more processing unit applications (PUA). +Each processing unit application is executed on one processing unit (PU) and +hosts one or more application processes (AP). ## Typical Workflow diff --git a/docs/tools/hardware-management-mcp-tools.md b/docs/tools/hardware-management-mcp-tools.md index ed1057c..0aeb31f 100644 --- a/docs/tools/hardware-management-mcp-tools.md +++ b/docs/tools/hardware-management-mcp-tools.md @@ -1,8 +1,8 @@ # Hardware Management Tools -**Domain:** Physical hardware platforms, imported topologies, and no-hardware workflows +**Domain:** Physical hardware platforms and imported topologies -Use this domain to create the topology required by hardware-facing I/O blocks. Choose a physical platform, an imported `.htfx` topology, or a no-hardware path before assigning channels. +Use this domain to create the topology required by hardware-facing I/O blocks. Choose a physical platform or an imported `.htfx` topology before assigning channels. For a no-hardware or VEOS build, add a processing unit application via [Application Management](app-management-mcp-tools.md) instead. ## Tool Contract @@ -15,7 +15,6 @@ Use this domain to create the topology required by hardware-facing I/O blocks. C | `list_platforms` | List registered platforms. | Read-only. | | `refresh_platforms` | Refresh platform information. | May contact physical hardware. | | `add_hardware_element` | Add a generic hardware element to a platform. | Use the runtime schema for supported element values. | -| `add_application_processing_unit` | Add processing support for VEOS or no-hardware workflows. | Do not use a physical-platform tool for this path. | ## Supported Platform Types @@ -25,7 +24,7 @@ Use this domain to create the topology required by hardware-facing I/O blocks. C 1. **Physical hardware:** Call `add_hardware_platform`, then use its returned platform name for later scan or assignment operations. 2. **Existing topology file:** Call `import_hardware_topology` with an `.htfx`path. -3. **VEOS or no physical hardware:** Call `add_application_processing_unit`. +3. **VEOS or no physical hardware:** Add a processing unit application — see [Application Management](app-management-mcp-tools.md). After physical hardware is available, use the Bus Access tools to assign bus I/O function blocks and channel sets. diff --git a/docs/tools/model-topology-mcp-tools.md b/docs/tools/model-topology-mcp-tools.md index 90aa7f2..4232c52 100644 --- a/docs/tools/model-topology-mcp-tools.md +++ b/docs/tools/model-topology-mcp-tools.md @@ -13,7 +13,7 @@ ports available for connection, and create application processes. | `replace_model` | Replace an existing model with another file. | Destructive because it changes the active model topology. | | `remove_model` | Remove a model from the project. | Destructive; related connections can be removed. | | `analyze_models` | Analyze loaded models and prepare their public interfaces. | May take time for large models. | -| `create_application_process` | Create an application process with a default periodic task. | Requires a processing-unit path. | +| `create_application_process` | Create an application process with a default periodic task. | Requires a processing unit application. | | `list_models` | List loaded models and file paths. | Read-only. | | `add_model_to_signal_chain` | Make all ports of one model available for connection. | Use for bulk port exposure. | | `add_model_port_to_signal_chain` | Make one named model port available. | Use `list_model_ports` first when the port name is unknown. |