UniteLabs

Low-level Control

Using the RuntimeController and execution channel to prepare methods and send XML commands to FluentControl at runtime.

Once a method is running and the XML channel is open, the FluentControl connector acts as an XML message bus. Each call to execute_command() sends one command string and awaits acknowledgement from FluentControl before returning; commands are processed sequentially.

This page covers the low-level XML-channel workflow. It uses the prerequisites from Connect & Run a Method. The service object exposes runtime_controller and execution_channel_controller.

Connect to the Connector

Retrieve the FluentControl service by the connector name registered in the platform. The name must match exactly.

from unitelabs.sdk import AsyncApiClient

client = AsyncApiClient()
service = await client.get_service_by_name("Tecan FluentControl")

XML-channel workflow

Use the low-level runtime_controller and execution_channel_controller when you need to stream XML commands into the running method.

FluentControl state model: FluentControl tracks its own process state, exposed as runtime_controller.subscribe_state(). The two states relevant to this page are EditMode (no method running, ready to prepare one) and any RunMode* state (a method is loaded or running). Tecan calls being in a RunMode* state with the XML channel open Advanced Worklist Execution. This is not a separate state value, only the name for "running with the XML channel active." Two RunMode* states are called out explicitly elsewhere on this page: RunModeResetTips (tip-reset prompt after starting) and RunModeQueryVariables (startup variable query). The troubleshooting term "stuck in RunMode" refers to being stuck in any of these states instead of returning to EditMode.

Check and reset the channel

Before starting a new session, verify that no XML channel is already open from a previous run. If one is, close it before proceeding.

import asyncio

await asyncio.sleep(2)  # let the system state settle before reading channel status
is_alive = await service.execution_channel_controller.get_is_alive()

if is_alive:
    channel = await service.execution_channel_controller.get_channel()
    print(f"Closing open channel: {channel}")
    await service.execution_channel_controller.finish_command()
    await service.runtime_controller.close_method()
If get_is_alive() returns True, FluentControl is still in Advanced Worklist Execution mode from a prior run. Calling prepare_method() in this state will raise an exception. FluentControl cannot accept a new method preparation while the previous execution channel is still open. Call finish_command() to close the channel, then close_method() to return to EditMode (see Close the method below) before preparing a new method.

List available methods

Use runtime_controller.get_runnable_methods() to retrieve the method names available in the connected FluentControl software.

methods = await service.runtime_controller.get_runnable_methods()
print(methods)
# ["plate_prep", "dilution_series", "demo"]

Prepare and start the method

Prepare the method by name, then start it. run_method() returns before FluentControl opens the XML channel, so wait until get_is_alive() returns True before sending any commands.

await service.runtime_controller.prepare_method(method="plate_prep")
await service.runtime_controller.run_method()

# Poll until the XML channel is open, with a timeout
for _ in range(20):
    if await service.execution_channel_controller.get_is_alive():
        break
    await asyncio.sleep(0.5)
else:
    raise TimeoutError("XML channel did not open within 10 seconds")
Until the channel is open, get_is_alive() returns False and execute_command() raises InvalidState. Adjust the timeout in the loop to your environment.

Close the method

Closing the session requires two distinct steps, in order.

Step 1: finish_command() sends the finish signal through the XML execution channel. FluentControl closes the channel on its side; the instrument no longer accepts XML commands after this call, but the method itself is not yet closed.

Step 2: close_method() closes the method on the instrument and waits for FluentControl to return to EditMode. Skipping this step leaves the method loaded, which blocks the next prepare_method() call.

await service.execution_channel_controller.finish_command()  # close XML channel
await service.runtime_controller.close_method()               # close method → EditMode
Call finish_command() before close_method(). close_method() waits up to 180 seconds for FluentControl to return to EditMode, and raises InvalidState if no method is loaded.

Checking the active channel

During development, use get_channel() to confirm the XML channel is open before sending commands.

channel = await service.execution_channel_controller.get_channel()
print(f"XML channel active on: {channel}")

Sending a command

Pass any XML command string to execute_command(). The call blocks until FluentControl acknowledges the command.

await service.execution_channel_controller.execute_command(command=xml_command_string)

The ScriptGroup envelope

Every command sent to FluentControl must be wrapped in a ScriptGroup XML envelope. The command itself goes inside <Objects>, wrapped in an <Object Type="..."> element using Tecan's scripting namespace (Tecan.Core.Scripting.*). For example, a UserPromptStatement that blocks until the operator presses OK:

<ScriptGroup>
  <Objects>
    <Object Type="Tecan.Core.Scripting.UserPromptStatement">
      <UserPromptStatement>
        <Prompt>Verify the deck layout, then press OK to continue</Prompt>
        <AutoClose>False</AutoClose>
        <Timeout>1</Timeout>
        <SoundFile></SoundFile>
        <RepeatSound>False</RepeatSound>
        <ChangeStatusLightColor>False</ChangeStatusLightColor>
        <StatusLightColorString>#FFFFFF00</StatusLightColorString>
        <IsBreakpoint>False</IsBreakpoint>
        <IsDisabledForExecution>False</IsDisabledForExecution>
        <LineNumber>1</LineNumber>
      </UserPromptStatement>
    </Object>
  </Objects>
  <Name></Name>
  <IsBreakpoint>False</IsBreakpoint>
  <IsDisabledForExecution>False</IsDisabledForExecution>
  <LineNumber>0</LineNumber>
</ScriptGroup>

Build helper functions to assemble and wrap the <Object Type="..."> element for other command types used in your workflow.

Tecan terminology: FluentControl calls the physical surface of the instrument the worktable — this is the same concept as the deck in Hamilton and Agilent instruments. Carrier names and site numbers referenced in XML commands must match the worktable configuration defined in your FluentControl installation.

Discovering XML command structures

FluentControl stores methods as XML internally. To find the correct XML structure for any command type — including operator prompts, labware registration, and pipetting (LiHa) commands — open the FluentControl script editor, add the desired step visually, then inspect or export the resulting .fcs method file. The XML can be read directly from there and adapted for use in execute_command().

Last updated