Skip to content

Add support for Hamilton MLPrep #407

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Add support for Hamilton MLPrep #407

wants to merge 2 commits into from

Conversation

rickwierenga
Copy link
Member

A (nearly) complete firmware implementation for the Hamilton MLPrep, including the ip layer, harp2 (hamilton robot protocol?) and hoi2. Tested the big five (setup, tip pickup, tip drop, aspirate, dispense) on ours. This is the world's first interface to the Prep that doesn't use the internal computer & touch screen: you can now properly program it.

Plug your computer into the robot's ethernet port (should be a passthrough to the internal board). Assign a static ip to your computer in the 192.168.100.x range, but not 192.168.100.102 since that's the robot's static ip.

TODO:

  • properly define the PrepDeck resource
  • verify it works on other Preps, the harp addressing I haven't figured out 100% yet
  • core grippers
  • error handling
  • manual channel movement works when specifying full xyz coordinates for a channel, but can we do individual dimensions like STAR?

@BioCam
Copy link
Contributor

BioCam commented Feb 24, 2025

In the branch of this PR, I'm receiving the following error:

from pylabrobot.liquid_handling.backends.hamilton.prep import Prep
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
Cell In[7], line 1
----> 1 from pylabrobot.liquid_handling.backends.hamilton.prep import Prep

File /mnt/c/Users/cmoschner/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/prep.py:10
      7 from typing import Any, List, Optional, Tuple, Union
      9 from pylabrobot.liquid_handling.backends import LiquidHandlerBackend
---> 10 from pylabrobot.liquid_handling.standard import (
     11   Aspiration,
     12   AspirationContainer,
     13   AspirationPlate,
     14   Dispense,
     15   DispenseContainer,
     16   DispensePlate,
     17   Drop,
     18   DropTipRack,
     19   Pickup,
     20   PickupTipRack,
     21   ResourceDrop,
     22   ResourceMove,
     23   ResourcePickup,
     24 )
     27 class ParameterTypes(Enum):
     28   Void = 0

ImportError: cannot import name 'Aspiration' from 'pylabrobot.liquid_handling.standard' (/mnt/c/Users/camillomoschner/pylabrobot/pylabrobot/liquid_handling/standard.py)

@rickwierenga, should this be updated to SingleChannelAspiration (etc.)?

@rickwierenga
Copy link
Member Author

yeah the prep branch was quite old, will fix soon, thanks

@BioCam
Copy link
Contributor

BioCam commented Feb 24, 2025

I've updated the standard.py command imports to their latest version (though it seems I cannot submit a commit to this PR?), and can then import Prep without a problem.

Next issue:

Sending command: 440006300000020004000400010001000015010002134000000000000103010000021701020000001e001a001701020001002800040000808f4328000400000040401f000000
Received response: 7a0006300000010001000015020004000400010002057600000000000105010000022100020015000f0050003078303030312e3078303045382e3078303230323a307830312c3078303030312c3078304630333b3078303030312e3078303045382e3078303230313a307830312c3078303030322c30783046303300
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[7], line 3
      1 lh = LiquidHandler(backend=backend, deck=PrepDeck())
----> 3 await lh.setup()
      4 vis = Visualizer(resource=lh)
      5 await vis.setup()

File /mnt/c/Users/cmoschner/pylabrobot/pylabrobot/liquid_handling/liquid_handler.py:172, in LiquidHandler.setup(self, **backend_kwargs)
    170 self.backend.set_deck(self.deck)
    171 self.backend.set_heads(head=self.head, head96=self.head96)
--> 172 await super().setup(**backend_kwargs)
    174 self.head = {c: TipTracker(thing=f"Channel {c}") for c in range(self.backend.num_channels)}
    175 self.head96 = {c: TipTracker(thing=f"Channel {c}") for c in range(96)}

File /mnt/c/Users/cmoschner/pylabrobot/pylabrobot/machines/machine.py:63, in Machine.setup(self, **backend_kwargs)
     62 async def setup(self, **backend_kwargs):
---> 63   await self.backend.setup(**backend_kwargs)
     64   self._setup_finished = True

File /mnt/c/Users/cmoschner/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/prep.py:267, in Prep.setup(self)
    264 self.socket.connect((self.host, self.port))
    265 self.socket.settimeout(30)
--> 267 await self.initialize(
    268   tip_drop_params=Prep.InitTipDropParameters(
    269     default_values=True,
    270     x_position=287.0,
    271     rolloff_distance=3,
    272     channel_parameters=[],
    273   ),
    274   smart=False,
    275 )
    277 await super().setup()

File /mnt/c/Users/cmoschner/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/prep.py:3249, in Prep.initialize(self, tip_drop_params, smart, timeout)
   3243 async def initialize(
   3244   self,
   3245   tip_drop_params: InitTipDropParameters,
   3246   smart: bool = False,
   3247   timeout: Optional[float] = None,
   3248 ) -> None:
-> 3249   return await self.send_command(
   3250     command_id=1,
   3251     parameters=[
   3252       (smart, ParameterTypes.Bool),
   3253       (tip_drop_params, ParameterTypes.Structure),
   3254     ],
   3255     timeout=timeout,
   3256     harp_source=self.source_address,
   3257     harp_destination=self.destination_address,
   3258   )

File /mnt/c/Users/cmoschner/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/prep.py:378, in Prep.send_command(self, command_id, parameters, harp_source, harp_destination, hoi_action, timeout)
    375 response = self.socket.recv(1024)
    376 print("Received response:", response.hex())
--> 378 self._decode_response(response)

File /mnt/c/Users/cmoschner/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/prep.py:352, in Prep._decode_response(self, response)
    350 if len(fragments) > 0 and fragments[0]["format"] == ParameterTypes.HcResult:
    351   if fragments[0]["fragment_data"] != 0:
--> 352     raise ValueError(f"Command failed with error code {fragments[0]['fragment_data']}")
    354 return

ValueError: Command failed with error code 21

I can see the channels starting to initialise (even the fixed-distance 8-channel head), i.e. move to the front of the arm (performing a flagsearch?), followed by moving into their "default" position.
But, immediately after the channels moved into their "default" position this error appears

@rickwierenga
Copy link
Member Author

I've updated the standard.py command imports to their latest version (though it seems I cannot submit a commit to this PR?), and can then import Prep without a problem.

based, can make a branch on your repo and PR into plr:prep?

ValueError: Command failed with error code 21

As far as I understand the prep firmware, this maps to GenericMultipleErrorsReported = 0x0015 (0x15 = 0d21).

Printing the fragments in this string I get:

Fragments: [{'format': <ParameterTypes.HcResult: 33>, 'flags': 0, 'length': 6, 'is_padded': False, 'fragment_data': 21}, {'format': <ParameterTypes.String: 15>, 'flags': 0, 'length': 84, 'is_padded': False, 'fragment_data': '0x0001.0x00E8.0x0202:0x01,0x0001,0x0F03;0x0001.0x00E8.0x0201:0x01,0x0002,0x0F03'}]

... i don't know what '0x0001.0x00E8.0x0202:0x01,0x0001,0x0F03;0x0001.0x00E8.0x0201:0x01,0x0002,0x0F03' means (yet)

fyi, we don't have the 8 channel head

@rickwierenga
Copy link
Member Author

a similar error found in a trace file i got from the prep:

... Type=Error, Src=, Dst=, Message=Source NodeID:          0x0002.0x0009.0x0004 Destination NodeID:     0x0001.0x0001.0x1500 Interface ID:           1 (0x01) Action ID:              1 (0x0001) Harp2 Sequence Number:  57 (0x39) Result:                 GenericMultipleErrorsReported (0x0015) Approximate Time Sent:  12:01:11.123_942 Timeout:                60000 Additional Information: Exception of type ''Hamilton.Components.TransportLayer.Protocols.HoiException'' was thrown. , Transport=None.";Reno Communication Manager;Communication;Error;Server;0b1976bbe32945f98752c59ffddc503a;;
... Type=ApplicationEntry, Src=, Dst=, Message=Exception type: ComLinkException Message:        0x0001.0x0001.0x2200:0x01,0x0006,0x0F04 (Hamilton.MLPrep.MLPrepSystem.MLPrepRoot.ChannelCoordinator.ChannelXYZCoordinator:IChannelXYZCoordinator,MoveXYZAbsolute,A coordinated movement was stopped early. See following errors for additional details.);0x0001.0x00EC.0x0204:0x01,0x0002,0x0F03 (Hamilton.MLPrep.MLPrepSystem.MLPrepRoot.RearChannel.ZAxis.ZDrive:IZDrive,MoveAbsolute,Motor stall detected.) Source:         Hamilton.Components.TransportLayer.ComLink    at Hamilton.Components.TransportLayer.ObjectInterfaceCommunication.ComLink.SendAndReceiveEx(HarpAddress srcAddress, HarpAddress dstAddress, Byte interfaceID, Hoi2Action action, UInt16 actionID, Object[]& inParameters, Object[]& outParameters, Int32 timeout)    at Hamilton.MLPrep.CMLPrep.Initialize(Boolean smart, InitTipDropParameters tipDropParams, TimeSpan timeout)    at Hamilton.XRP2.Instrument.ModuleN.Common.Firmware.MicroLabPrepFirmware.<>c__DisplayClass75_0.<Initialize>b__0() in C:\Agents\ML Prep Agent 5\_work\506\s\src\ModuleN.Common\Firmware\MicroLabPrepFirmware.cs:line 782    at Hamilton.XRP2.Instrument.ModuleN.Common.Firmware.ComLinkExceptionScope.Execute(Action action, IDictionary`2 context) in C:\Agents\ML Prep Agent 5\_work\506\s\src\ModuleN.Common\ComLinkExceptionScope.cs:line 69    at Hamilton.XRP2.Instrument.ModuleN.Common.Firmware.MicroLabPrepFirmware.Initialize(Boolean smart, InitTipDropParameters tipDropParams) in C:\Agents\ML Prep Agent 5\_work\506\s\src\ModuleN.Common\Firmware\MicroLabPrepFirmware.cs:line 779    at Hamilton.XRP2.Instrument.MicroLabPrep.Firmware.MicroLabPrepFirmwareProxy.<>c__DisplayClass22_0.<Initialize>b__0() in C:\Agents\ML Prep Agent 5\_work\506\s\src\ModuleN.Common\Firmware\MicroLabPrepFirmwareProxy.cs:line 73    at Hamilton.XRP2.Instrument.FirmwareProxy.InternalProxy(Action action, Action simulatedAction) in C:\Agents\ML Prep Agent 5\_work\506\s\src\ModuleN.Common\FirmwareProxy.cs:line 66    at Hamilton.XRP2.Instrument.FirmwareProxy.Proxy(Action action, Action simulatedAction) in C:\Agents\ML Prep Agent 5\_work\506\s\src\ModuleN.Common\FirmwareProxy.cs:line 86    at Hamilton.XRP2.Instrument.MicroLabPrep.Firmware.MicroLabPrepFirmwareProxy.Initialize(Boolean smart, InitTipDropParameters tipDropParameters) in C:\Agents\ML Prep Agent 5\_work\506\s\src\ModuleN.Common\Firmware\MicroLabPrepFirmwareProxy.cs:line 75    at Hamilton.XRP2.Instrument.MicroLabPrep.Common.MicroLabPrepFirmwareAdapter.Initialize(Boolean smart) in C:\Agents\ML Prep Agent 5\_work\506\s\src\ModuleN.PipettingArmControllerEngine.Common\Firmware\MicroLabPrepFirmwareAdapter.cs:line 51    at Hamilton.XRP2.Instrument.MicroLabPrep.Action.Handlers.ExecuteInitializeActionHandler.Execute(IMicroLabPrepPipettingArm controller, Initialize command, CxbeReturn returnObject) in C:\Agents\ML Prep Agent 5\_work\506\s\src\ModuleN.PipetteArmController\Action\Handlers\ExecuteInitializeActionHandler.cs:line 105    at Hamilton.XRP2.Instrument.Commands.Initialize.InternalExecute(IList`1 parameters, CxbeReturn returnObject) in C:\Agents\ML Prep Agent 5\_work\506\s\src\ModuleN.PipetteArmController\Commands\Initialize.cs:line 89    at Hamilton.XRP2.Instrument.Commands.MicroLabPrepCommand`1.ExecuteCommand(IList`1 cobjParameter, CxbeReturn objReturn) in C:\Agents\ML Prep Agent 5\_work\506\s\src\ModuleN.PipettingArmControllerEngine.Common\Commands\MicroLabPrepCommand.cs:line 236    at Hamilton.XRP2.Run.CxbeBaseCommand.Execute(IList`1 cobjParameter, CxbeReturn objReturn)    at Hamilton.XRP2.Run.CxbeContainerCommand.ExecuteCommand(IList`1 cobjParameter, CxbeReturn objReturn)    at Hamilton.XRP2.Run.SchedulerCommand.CxseActivity.ExecuteCommand(IList`1 cobjParameter, CxbeReturn objReturn)    at Hamilton.XRP2.Run.CxbeBaseCommand.Execute(IList`1 cobjParameter, CxbeReturn objReturn)    at Hamilton.XRP2.Run.CxleRuntimeInternal.Execute(String strEntryPointId, IList`1 cobjParameter)    at Hamilton.XRP2.Run.Scheduler.Utils.XslEngineAdapter.Execute(String entryPointId, IList`1 parameter)    at Hamilton.XRP2.Run.Scheduler.CsdeActivity.<>c__DisplayClass239_0.<ActivityThreadFunction>b__0()    at Hamilton.XRP2.Run.Scheduler.CsdeSchedulerInternal.UnhandledExceptionFrame(Boolean bBeginAbortRun, Action objAction)    at Hamilton.XRP2.Run.Scheduler.CsdeActivity.ActivityThreadFunction(Object objState)    at Hamilton.XRP2.Run.Scheduler.CsdeThreadPoolThread.ThreadStart()    at System.Threading.ThreadHelper.ThreadStart_Context(Object state)    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)    at System.Threading.ThreadHelper.ThreadStart()  ----inner exception details---- Exception type: HoiException Message:        Exception of type ''Hamilton.Components.TransportLayer.Protocols.HoiException'' was thrown. Source:         Hamilton.Components.TransportLayer.Protocols    at Hamilton.Components.TransportLayer.Protocols.HoiProtocol2.SendRecv(HarpAddress srcAddress, HarpAddress dstAddress, HcResult result, HoiPacket2 packet, Boolean response, Int32 timeout)    at Hamilton.Components.TransportLayer.ObjectInterfaceCommunication.ComLink.SendAndReceiveEx(HarpAddress srcAddress, HarpAddress dstAddress, Byte interfaceID, Hoi2Action action, UInt16 actionID, Object[]& inParameters, Object[]& outParameters, Int32 timeout), Transport=None.";Reno Communication Manager;Communication;Information;Server;0b1976bbe32945f98752c59ffddc503a;;147

this part stands out:

0x0001.0x00EC.0x0204 : 0x01,0x0002,0x0F03
(Hamilton.MLPrep.MLPrepSystem.MLPrepRoot.RearChannel.ZAxis.ZDrive:
IZDrive, MoveAbsolute, Motor stall detected.)

(spacing added for readability)

0x0F04 is 0d3844, which is in the same range as other Prep.Error objects I found. More importantly, it's the same number as yours: so that would mean 'motor stalled'.

0x0001.0x00E8.0x0202
0x0001.0x00E8.0x0201

the part before the colon could be the address for the two 'heads' (1 & 8 channel). do you have two?

One of the parameters of Prep.initialize (called in setup), is tip_drop_params: InitTipDropParameters, which has channel_parameters: list["Prep.DropTipParameters"] as an attr. It is not set, but could it be that the prep is gonna discard tips and has some default values that don't work with the 8 channel head? Just guessing. Does it physically look like this could be true? I would play around with these values a little bit, since it's impossible for you to get a real log file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants