Maintenance scripts for STAR(let)s?

yes, the Venus daily maintenance macro. Unfortunately I don’t have access to the software myself - this was discovered during PM

I think it’d be helpful to determine the full set of tests the macro runs and then replicate each of those - maybe in collab w Hamilton?

1 Like

perhaps related, i recently started adding some qc scripts for the STAR pylabrobot/tools/qc/hamilton at main · PyLabRobot/pylabrobot · GitHub. it is very early stage. see background in PR 599

i will take a look at the daily maintenance script. i think it should be easy to convert to PLR

:eyes:

We just have to bite our way through a good chunk of HSL for the logic part

HSL → Python/PLR convertor?

:joy: I don’t think we have to go quite that far, that sounds like a nightmare since I am not aware of any syntax documentation for HSL?

If I find some time I’d just read the logic, identify the parameter ranges for acceptance and recreate the measurement commands from the trc file?

1 Like

I was working in the macro programming language and I had my friend Claude take a quick look at some maintenance logs. Below is the logic and some tips for implementing into PyLabRobot.

I didn’t make the changes/additions myself because I’m not all that comfortable with it at this point.

Hope this helps!

Hamilton STARlet Weekly Maintenance Process Analysis

PyLabRobot Conversion Project Overview

Date: 2025-01-14
System: Hamilton Microlab STARlet (Serial: XXXX)
Software: Phoenix 4.5.0.7977 / STARlet 4.5.0.5217
Firmware: C0 Master 7.6S, P1/P2 Channels 4.0S, X0 Drive 1.4S


Executive Summary

This document analyzes the Hamilton STARlet weekly maintenance process to enable PyLabRobot implementation. The maintenance involves system initialization, user-guided cleaning procedures, pressure tightness testing, and liquid level detection verification.

System Architecture

Hardware Configuration

  • Main Controller (C0): Master module controlling system coordination
  • Pipetting Channels (P1/P2): Two 1000μl channels with pressure sensors
  • X-Drive (X0): Horizontal positioning system
  • Teaching Needles: Special maintenance tools for pressure testing

Communication Protocol

  • High-Level: Hamilton Phoenix software commands
  • Low-Level: Direct firmware commands via USB/CAN protocol
  • Command Format: ModuleCommand + ID + Parameters (e.g., C0RQid0101)

Maintenance Workflow Analysis

1. System Initialization (16:24:38-16:24:48)

# Equivalent PyLabRobot sequence:
device_enumeration()
firmware_version_check()
channel_configuration_verification()
system_status_query()

Hamilton Firmware Commands:

# Device identification and status
< C0RQid0101                    # Request status
> C0RQid0101rq0000              # Response: OK

< C0RIid0103                    # Read instrument info  
> C0RIid0103er00/00si2017-01-31snXXXX  # Serial number and date

< C0RMid0105                    # Read module configuration
> C0RMid0105er00/00kb01kp02 C00000 X00000 P10000 P20000  # Module status

< P1RFid0107                    # Read P1 firmware version
> P1RFid0107rf4.0S j 2022-03-16 # Firmware version response

< P2RFid0109                    # Read P2 firmware version  
> P2RFid0109rf4.0S j 2022-03-16 # Firmware version response

< C0QMid0104                    # Query system configuration
> C0QMid0104er00/00ka000001xt30xa30xw08000xl01xr00xm03500xx11400ys090xu3540xv3700yu0800kl360kc0yx0060ke00000000xn00xo00ym6065kr0km360

Key Commands:

  • C0RI - Read instrument info (serial number)
  • C0RM - Read module configuration
  • P1RF/P2RF - Read firmware versions
  • C0QM - Query system configuration

2. User Interaction Prompts (16:24:48-16:25:00)

# User guidance sequence:
prompt_remove_carriers()
prompt_clean_deck() 
prompt_empty_tip_waste()
confirm_teaching_needles_installed()

Hamilton User Interaction Examples:

# Initial maintenance confirmation
2025-06-03 16:24:48> SYSTEM : Message Box - start; <Weekly maintenance - deck and tip waste>
Available button(s): <Yes>, <No>, Default button: <Yes>
Message: <Do you want to execute the weekly maintenance for deck and waste?>
2025-06-03 16:24:50> SYSTEM : Message Box - complete; <Yes (6)>

# Carrier removal prompt
2025-06-03 16:24:52> SYSTEM : Message Box - start; <Weekly maintenance - carriers>
Available button(s): <OK>, <Cancel>, Default button: <OK>  
Message: <Remove all carriers from the deck, clean all carriers and press OK to continue.
(See Operator's Manual for use of cleaning, disinfecting and decontaminating fluids.)
Cancel will abort the weekly maintenance.>
2025-06-03 16:24:53> SYSTEM : Message Box - complete; <OK (1)>

# Deck cleaning prompt
2025-06-03 16:24:53> SYSTEM : Message Box - start; <Weekly maintenance - deck>
Available button(s): <OK>, Default button: <OK>
Message: <Open the front cover, clean the deck and press OK to continue.
(See Operator's Manual for use of cleaning, disinfecting and decontaminating fluids.)>
2025-06-03 16:24:54> SYSTEM : Message Box - complete; <OK (1)>

# Tip waste cleaning prompt  
2025-06-03 16:24:55> SYSTEM : Message Box - start; <Weekly maintenance - tip waste>
Available button(s): <OK>, Default button: <OK>
Message: <Open the front cover, empty and clean the tip waste and press OK to continue.
(See Operator's Manual for use of cleaning, disinfecting and decontaminating fluids.)>
2025-06-03 16:24:56> SYSTEM : Message Box - complete; <OK (1)>

# Tightness test confirmation
2025-06-03 16:25:00> SYSTEM : Message Box - start; <Weekly Maintenance - Tightness Check 1000μl Channel>
Available button(s): <Yes>, <No>, Default button: <Yes>
Message: <Do you want to execute the 1000μl Channel tightness check?
Make sure that 2 1000μl teaching needles are installed.>
2025-06-03 16:25:01> SYSTEM : Message Box - complete; <Yes (6)>

User Actions Required:

  • Remove all carriers from deck
  • Clean deck surfaces
  • Empty and clean tip waste
  • Install 2x 1000μl teaching needles

3. Pressure Tightness Testing (16:25:01-16:26:31)

# Pressure testing sequence:
teaching_needle_pickup()
baseline_pressure_calibration()  # BP command
positive_pressure_test()         # DS + RP commands  
negative_pressure_test()         # DS + RP commands
pressure_tolerance_validation()
teaching_needle_ejection()

Hamilton Firmware Commands:

# Teaching needle pickup and baseline calibration
< PXAAid0208dp20000              # Position channels for needle pickup
> PXAAid0208er00                 # Acknowledge

< PXBPid0212bp0                  # Baseline pressure calibration
> PXBPid0212er00                 # Calibration complete

# Positive pressure test
< P1DSid0213ds01250dt1           # Dosing steps: 1250 steps, dispense direction
> P1DSid0213er00                 # Movement complete
< P1RPid0214                     # Read pressure channel 1
> P1RPid0214rp+4082              # Pressure reading: +4082 Pascal

< P2DSid0215ds01250dt1           # Channel 2 dosing steps
> P2DSid0215er00                 # Movement complete  
< P2RPid0216                     # Read pressure channel 2
> P2RPid0216rp+4057              # Pressure reading: +4057 Pascal

# Wait 20 seconds, then re-read for leak test
< P1RPid0219                     # Read pressure after delay
> P1RPid0219rp+4077              # Pressure after 20s: +4077 Pascal (5 Pa loss)
< P2RPid0220                     # Read pressure channel 2  
> P2RPid0220rp+4052              # Pressure after 20s: +4052 Pascal (5 Pa loss)

# Negative pressure test  
< P1DSid0228ds00800dt0           # Dosing steps: 800 steps, aspirate direction
> P1DSid0228er00                 # Movement complete
< P1RPid0229                     # Read pressure
> P1RPid0229rp-4370              # Negative pressure: -4370 Pascal

< P2DSid0230ds00800dt0           # Channel 2 aspirate
> P2DSid0230er00                 # Movement complete
< P2RPid0231                     # Read pressure  
> P2RPid0231rp-4365              # Negative pressure: -4365 Pascal

# Leak test after 20 seconds
< P1RPid0234                     # Read pressure after delay
> P1RPid0234rp-4345              # Pressure after 20s: -4345 Pascal (25 Pa loss)
< P2RPid0235                     # Read pressure channel 2
> P2RPid0235rp-4340              # Pressure after 20s: -4340 Pascal (25 Pa loss)

Key Parameters:

  • Target Pressures: ±4000 Pascal range
  • Tolerance: <1500 Pascal pressure loss over 20 seconds
  • Commands:
    • BP - Baseline pressure calibration
    • DS - Dosing drive steps (dt0=aspirate, dt1=dispense)
    • RP - Read pressure values

4. cLLD Testing (16:26:37-16:27:01)

# Capacitive liquid level detection test:
teaching_needle_pickup()
clld_initialization()     # AL command
measurement_setup()       # JM command  
level_detection_test()    # ZL + RN + KZ commands
clld_validation()
teaching_needle_ejection()

Hamilton Firmware Commands:

# cLLD initialization and setup
< PXALid0245                     # Liquid level detection initialization  
> PXALid0245er00                 # Initialization complete

< PXJMid0246                     # Measurement mode setup
> PXJMid0246er00/00              # Setup complete

# Level detection test sequence  
< PXZLid0247                     # Z-level detection command
> PXZLid0247er70                 # Error 70: Expected for air detection

< PXRNid0248                     # Needle contact detection
> PXRNid0248rn0                  # Contact status: 0 (no contact)

< PXZLid0249                     # Retry Z-level detection
> PXZLid0249er00                 # Success: Level detected

< PXRNid0250                     # Needle contact verification
> PXRNid0250rn1                  # Contact status: 1 (contact detected)

< PXKZid0251                     # Capacitance measurement
> PXKZid0251er00/00              # Measurement complete

# Second channel cLLD test
< PXJMid0252                     # Channel 2 measurement setup
> PXJMid0252er00/00              # Setup complete

< PXZLid0253                     # Z-level detection
> PXZLid0253er70                 # Expected air detection error

< PXRNid0254                     # Contact detection  
> PXRNid0254rn0                  # No contact initially

< PXZLid0255                     # Retry detection
> PXZLid0255er00                 # Level found

< PXRNid0256                     # Final contact check
> PXRNid0256rn1                  # Contact confirmed

< PXKZid0257                     # Final capacitance measurement
> PXKZid0257er00/00              # Measurement successful

Key Commands:

  • AL - Liquid level detection initialization
  • JM - Measurement mode setup
  • ZL - Z-level detection with error handling
  • RN - Needle contact detection
  • KZ - Capacitance measurement

5. System Cleanup (16:27:01-16:27:24)

# Cleanup sequence:
return_to_safe_positions()
channel_spreading()
system_shutdown()
maintenance_completion_log()

Hamilton Cleanup Commands:

# System position reset
< C0RUid0117                     # Return to safe positions
> C0RUid0117er00/00ru00950 08000 30000 30000  # Position confirmation

# Channel cleanup and spreading
2025-06-03 16:27:22> Microlab STARlet : Clean up instrument - progress; Move up 1000ul channels
2025-06-03 16:27:22> Microlab STARlet : Clean up instrument - progress; Spreading channels

# Final system status
< C0AV                           # System availability check  
> C0AVer00/00                    # System ready

# Maintenance completion
2025-06-03 16:27:04> SYSTEM : Message Box - start; <Weekly maintenance - successful>
Available button(s): <OK>, Default button: <OK>
Message: <Weekly maintenance execute successfully completed>
2025-06-03 16:27:20> SYSTEM : Message Box - complete; <OK (1)>

6. Teaching Needle Management

# Teaching needle pickup sequence
2025-06-03 16:25:01> Microlab STARlet : 1000ul Channel Tip Pick Up (Single Step) - start;
2025-06-03 16:25:21> Microlab STARlet : 1000ul Channel Tip Pick Up (Single Step) - complete;
> channel 1: teachingNeedleBlock, 1 > channel 2: teachingNeedleBlock, 2

# Teaching needle ejection sequence  
2025-06-03 16:25:45> Microlab STARlet : 1000ul Channel Tip Eject (Single Step) - start;
2025-06-03 16:25:52> Microlab STARlet : 1000ul Channel Tip Eject (Single Step) - complete;
> channel 1: teachingNeedleBlock, 1 > channel 2: teachingNeedleBlock, 2

Critical Implementation Requirements

1. Command Translation Table

Hamilton Command Function Example PyLabRobot Equivalent
C0RI Read instrument info C0RIid0103er00/00si2017-01-31snXXXX device.get_serial_number()
P1RP/P2RP Read pressure P1RPid0214rp+4082 channel.read_pressure()
P1DS/P2DS Dosing steps P1DSid0213ds01250dt1er00 channel.move_plunger(steps=1250, direction='dispense')
PXBPbp0 Baseline pressure PXBPid0212bp0er00 channel.calibrate_pressure_baseline()
PXAA Initialize position PXAAid0208dp20000er00 channel.initialize(position=20000)
PXAL cLLD initialization PXALid0245er00 channel.initialize_liquid_detection()
PXJM Measurement mode PXJMid0246er00/00 channel.set_measurement_mode()
PXZL Z-level detection PXZLid0247er70 channel.detect_level()
PXRN Needle contact PXRNid0248rn0 channel.check_needle_contact()
PXKZ Capacitance measure PXKZid0251er00/00 channel.measure_capacitance()

2. Pressure Testing Specifications

  • Positive Pressure: Target ~4000 Pa, tolerance ±200 Pa
  • Negative Pressure: Target ~-4000 Pa, tolerance ±200 Pa
  • Leak Test: <1500 Pa loss over 20 seconds
  • Stabilization: 2-3 second delays between measurements

3. Error Handling Requirements

  • Pressure range validation (max ±10000 Pa)
  • Teaching needle presence verification
  • Timeout handling for pressure stabilization
  • User abort capability with safe recovery

Hamilton Error Handling Examples:

# Normal successful response
< P1RPid0214
> P1RPid0214rp+4082              # Success: pressure value returned

# Error response format  
< PXZLid0247                     # Z-level detection
> PXZLid0247er70                 # Error 70: Expected for air detection

# Communication error
< C0RQid0101
> C0RQid0101er99/99              # Error 99: Communication failure

# Timeout handling
< P1DSid0213ds01250dt1
> [no response after timeout]    # Requires retry or abort

# USB connection status
! 15:30:56.836 8AF#8000#00: USB Info: CLOSE USB_READ_THREAD
! 15:31:13.392 8AF#8000#00: USB Info: CLOSE USB_READ_THREAD

Error Code Meanings:

  • er00/00 - Success, no errors
  • er70 - Expected cLLD error (air detection)
  • er99/99 - Communication failure
  • No response - Timeout, requires recovery

4. Timing Requirements

  • Pressure Stabilization: 2-3 seconds between measurements
  • Leak Testing: 20-second monitoring periods
  • User Timeouts: Configurable prompt timeouts
  • System Delays: Hardware-specific settling times

PyLabRobot Implementation Strategy

Phase 1: Core Infrastructure

  1. Hamilton firmware command abstraction layer
  2. Device enumeration and configuration system
  3. Error handling and recovery procedures
  4. Basic communication protocol implementation

Phase 2: Maintenance Procedures

  1. User interaction and prompting system
  2. Teaching needle management
  3. Pressure testing implementation
  4. cLLD testing procedures

Phase 3: Validation and Testing

  1. Test framework development
  2. Tolerance validation system
  3. Comprehensive logging and tracing
  4. Performance verification against Hamilton reference

Implementation Examples

Hamilton Command Interface

class HamiltonSTARlet:
    def __init__(self, device_id: str = "XXXX"):
        self.device_id = device_id
        self.channels = {1: HamiltonChannel(1), 2: HamiltonChannel(2)}
        
    def send_command(self, command: str, timeout: float = 5.0) -> str:
        """Send Hamilton firmware command and return response"""
        # Implementation would interface with PyLabRobot backend
        response = self._send_raw_command(command, timeout)
        return self._parse_response(response)
    
    def get_system_info(self) -> dict:
        """Get system configuration like Hamilton C0RI command"""
        response = self.send_command("C0RIid0001")
        # Parse: er00/00si2017-01-31snXXXX
        return self._parse_system_info(response)

class HamiltonChannel:
    def __init__(self, channel_id: int):
        self.channel_id = channel_id
        
    def read_pressure(self) -> int:
        """Read pressure like Hamilton P1RP command"""
        cmd = f"P{self.channel_id}RPid{self._get_next_id()}"
        response = self.send_command(cmd)
        # Parse: rp+4082 -> return 4082
        return self._parse_pressure(response)
        
    def move_plunger(self, steps: int, direction: str) -> bool:
        """Move plunger like Hamilton P1DS command"""
        dt_value = "1" if direction == "dispense" else "0"
        cmd = f"P{self.channel_id}DSid{self._get_next_id()}ds{steps:05d}dt{dt_value}"
        response = self.send_command(cmd)
        return response.startswith("er00")

Pressure Testing Implementation

class PressureTightnessTest:
    def __init__(self, device: HamiltonSTARlet):
        self.device = device
        self.tolerance_pa = 1500  # Max 1500 Pa loss allowed
        
    async def run_tightness_test(self) -> dict:
        """Execute complete pressure tightness test"""
        results = {}
        
        # Step 1: Teaching needle pickup
        await self._pickup_teaching_needles()
        
        # Step 2: Baseline calibration  
        await self._calibrate_baseline_pressure()
        
        try:
            # Step 3: Positive pressure test
            pos_results = await self._test_positive_pressure()
            results.update(pos_results)
            
            # Step 4: Negative pressure test  
            neg_results = await self._test_negative_pressure()
            results.update(neg_results)
            
        finally:
            # Step 5: Teaching needle ejection
            await self._eject_teaching_needles()
            
        return self._validate_results(results)
    
    async def _test_positive_pressure(self) -> dict:
        """Test positive pressure like Hamilton sequence"""
        results = {}
        
        for channel_id in [1, 2]:
            channel = self.device.channels[channel_id]
            
            # Build pressure: P1DSid0213ds01250dt1
            await channel.move_plunger(steps=1250, direction="dispense")
            
            # Read initial pressure: P1RPid0214 -> rp+4082
            initial_pressure = await channel.read_pressure()
            
            # Wait 20 seconds for stabilization
            await asyncio.sleep(20.0)
            
            # Read final pressure: P1RPid0219 -> rp+4077  
            final_pressure = await channel.read_pressure()
            
            # Calculate pressure loss
            pressure_loss = abs(initial_pressure - final_pressure)
            
            results[f"channel_{channel_id}_positive"] = {
                "initial_pressure": initial_pressure,
                "final_pressure": final_pressure, 
                "pressure_loss": pressure_loss,
                "pass": pressure_loss < self.tolerance_pa
            }
            
        return results

User Interaction System

class MaintenanceUserInterface:
    def __init__(self):
        self.logger = logging.getLogger(__name__)
        
    async def prompt_maintenance_start(self) -> bool:
        """Replicate Hamilton weekly maintenance start prompt"""
        message = (
            "Weekly maintenance - deck and tip waste\n\n"
            "Do you want to execute the weekly maintenance for deck and waste?"
        )
        return await self._show_yes_no_dialog(
            title="Weekly Maintenance", 
            message=message,
            default="Yes"
        )
    
    async def prompt_clean_deck(self) -> bool:
        """Replicate Hamilton deck cleaning prompt"""
        message = (
            "Open the front cover, clean the deck and press OK to continue.\n"
            "(See Operator's Manual for use of cleaning, disinfecting and "
            "decontaminating fluids.)"
        )
        return await self._show_ok_dialog(
            title="Weekly maintenance - deck",
            message=message
        )
        
    async def prompt_teaching_needles(self) -> bool:
        """Confirm teaching needles are installed"""
        message = (
            "Do you want to execute the 1000μl Channel tightness check?\n\n"
            "Make sure that 2 1000μl teaching needles are installed."
        )
        return await self._show_yes_no_dialog(
            title="Weekly Maintenance - Tightness Check 1000μl Channel",
            message=message,
            default="Yes"
        )

Key Challenges and Considerations

1. Hardware Abstraction

  • Direct firmware command translation to PyLabRobot APIs
  • Pressure sensor calibration and reading
  • Teaching needle handling mechanisms

2. Timing Sensitivity

  • Pressure stabilization requirements
  • Hardware response delays
  • Real-time user interaction handling

3. Safety and Validation

  • Pressure range limits and safety checks
  • Teaching needle presence verification
  • System state validation and recovery

4. User Experience

  • Clear maintenance instructions and prompts
  • Progress indication during long procedures
  • Error reporting and troubleshooting guidance

Success Metrics

  1. Functional Equivalence: All Hamilton maintenance procedures replicated
  2. Performance: Timing within ±10% of Hamilton reference
  3. Reliability: >99% successful completion rate
  4. Safety: Zero hardware damage incidents during testing
  5. Usability: Clear user guidance and error reporting

Next Steps

  1. Complete command mapping documentation
  2. Implement basic device communication layer
  3. Develop pressure testing procedures
  4. Create comprehensive test framework
  5. Validate against Hamilton reference behavior

This analysis provides the foundation for implementing Hamilton STARlet weekly maintenance procedures in PyLabRobot, ensuring functional equivalence while maintaining safety and reliability standards.

3 Likes

@JDesRo this is fantastic!

This high level overview of the steps it does is super useful. Now we actually know what a daily maintenance script is. I had no idea really :sweat_smile: I am very optimistic we can have this in PLR soon. I think as a qc script.

implementation wise, my intuition would be to write a macro->PLR translator (even if it needs a manual polish) to ensure accuracy and make it easy to port other scripts. LLMs sometimes lose track/forget parts when translating code.

  • The most challenging part of this would be to find the corresponding PLR methods for sending firmware commands (it is possible to just sent them straight up, but then we lose the validation we already have in PLR and makes the code less readable).
    • We need a firmware command → PLR method name lookup table. For that LLMs are perfect

is the maintenance script an .hsl or .med file or macro? or something else?

:joy:

3 Likes

They are HSL/STP/LAY files.

As I mentioned I am coding a few projects directly in the Macro/Direct command language. I’m not doing any traditional liquid handling so things like liquid classes and deck layouts aren’t important.

I’ve also been able to successfully hijack the CAN BUS and send direct commands for the creation of some external testing devices.

They do. They must be watched and maintained as a tool like anything else in this industry.

Glad this helps!

3 Likes

more noob questions:

  • what is in the lay files? standard labware? specialized calibration tools?
  • can the users change this?
  • did you give the hsl to claude?

sounds fascinating! started a new thread to ask more about this, to not distort the focus of this thread. would love to learn more

1 Like

also, the HSL scripts in your screenshot are different from the macro file format right? did you give the hsl file to claude?

I provided it the firmware reference for the C0(Master), PX(Pipette) etc. I then gave it the program trace that is generated by Venus and the raw Comtrace output and asked it to cross reference and provide some information on on the logic and importing this logic into PyLabAutomation.

4 Likes

The .lay’s are just blank decks with the waste trough, and 96 head waste, for each instrument size. The instrument uses the key’ed hole and teaching needles on the waste trough to do the CLLD check.

The pressure checks are done with the teaching needles.

Daily is one of the only scripts in .hsl I’ve seen that doesn’t have a deck attached at the beginning. It determines instrument size then pulls the correct deck

1 Like