Dev Discussion: Heaters, Shakers, HeaterShakers and Coolers

I just tried out the API for the Opentrons heater module (doesn’t shake). Opentrons also has a heater shaker. @CamilloMoschner (and @jonlaurent?) is working on the Hamilton HeaterShaker, and I’ll be doing the Inheco HeaterShaker hopefully next week depending on shipping.

For context, Inheco and possibly others also have devices that just shake.

Options for PLR:

  • We could add a heating module with a Heater frontend, where some backends will support shaking and others don’t.

  • The alternative is to have heating, shaking and heating_shaking modules in PLR. The HeaterShaker frontend would just subclass Heater and Shaker with maybe an added heat_and_shake method. HeaterShaker devices would implement both the HeaterBackend and ShakerBackend methods (just heat&get_temp / shake?). This seems to support everything with minimal code reuse and provides a clear API to the user.

Coolers should probably also be included because they essentially have the same API as heaters, just a different temperature range. Substitute Heater with TemperatureController?

What do you think? I hope to start implementation early next week.


Thank you for setting up this discussion, @rickwierenga. There is a lot to integrate and this is a nice way to coordinate.

A bit of background to the heater shaker integration I have performed so far:
In PLR PR#66 I differentiate between…

  1. direct device/module integration to a machine, i.e. make a new module essentially part of a machine. In this case PLR sends firmware commands to the machine which controls the module.
  2. connecting PLR directly to a module, i.e. PLR sends commands to the module for control. This module can then be physically placed onto the deck of a machine or anywhere else, e.g. there might not be enough space for it on the deck, then you might want to place it next to it and use an arm (e.g. iSWAP) to move a resource onto that module.

In this regards, may I first ask: For consistency and unambiguous communication, what do we want to call small extra items with independent functionalities like a heater-shaker or temperature-controller?

  • device?
  • module? (I started with, but then noticed that firmware commands use the word “module” already, and I think we should avoid re-using the same word for different purposes; I’m thinking about the use of words like “sequence” being used for DNA, protein, tip pattern, pipetting pattern, … or “channel” being used for colour channels in microscopy, virtual numbers in TV, engravements in microfluidics and pipettes in automation, … being quite confusing).

I.e. What do we want to call the superset of these small extra items in PLR and how do we explain their relationship to terms like machine, backend, liquid handler to users?
(I could actually see a lot of people using PLR simply to coordinate an array of heater-shakers on a bench with a Raspberry Pi :slight_smile: )

In regards to your post directly regarding discussion of how to integrate these devices into PLR:

I love your idea of having two master classes with subclasses that specify functions:

  • HeaterShaker
    • Heater
    • Cooler
    • Shaker
  • TemperatureController
    • Heat
    • Cool

I could imagine we simply call the class or subclass like

  • hhs = HeaterShaker("HHS") for instantiating a Hamilton Heater Shaker, or
  • temp_controller = TemperatureController("Opentrons_temp_module")

I.e. the master class identifies what we are asking it to identify and establishes the connection.

I am definitely in favour in minimising the necessary code writing directly in the automation script.

At least for the shaker we must have a plate_lock option in addition to possible heat and cool, not all shakers can/need to lock their cargo but many do.

I’ll paraphrase and expand what I said privately before for clarity in this thread, because I can’t find the original now: I think we should have two HSS backends, one for STAR and one for the direct connection. This way, you could initialize:

hss = STARHSS(device_number=1, star=star_backend)
hss = DirectHSS(serial="/dev/cu.*")


hs = HeaterShaker(backend=hss)

To me it makes a lot of sense to pass a STAR as a parameter to a HeaterShaker, with a tiny wrapper for fixing the device_number. We could consider moving the heater_shaker methods you added in #66 to STARHSS and just using the star for its send_command. This class can call await self.check_type_is_hhc(device_number) once on setup and then store the device id. I like this approach but as always happy to discuss alternatives.

I suggest we also call those Machine, equivalent to backend’d Resource. It doesn’t make sense to me to treat larger machines differently. And LiquidHandler is now a Resource subclass.

HSS should be HHS

1 Like

I was actually thinking:

  • TemperatureController:
    • set_target_temperature
    • get_current_temperature
  • Shaker
    • shake
    • stop
    • lock
  • HeaterShaker(TemperatureController, Shaker): subclass both
    • heat_and_shake

Sure, a Heater or Shaker front end can hen accept a HeaterShakerBackend if someone is interested in that. But, HeaterShaker only accepts HeaterShakerBackend.

Good point. Added that above. As with others, raise ImplementationError on the backend when not implemented on a machine.

Absolutely love this. Easy to track and fast to set up while giving full functionality of the small machines.

That makes sense. Machine it is (if nobody else has any other ideas).
So we can imagine placing small machines (e.g. shakers, coolers, …) into big machines (e.g. liquid handler).

1 Like

Yes, that simplifies things quite a bit.

Might I ask whether we can please use this online “white board” to draw a connection diagram between what we want the next iteration of the PLR architecture to look like?

PLR Architecture Brainstorm White Board

1 Like

first implementation


I really love your documentation. Thank you for this!!

1 Like

My Inheco thermoshake arrived yesterday. Getting this interface to work was a little more complicated than I thought, but here you go. I think this is the world’s first non-dll interface to Inheco machines.

(heating_shaking/ has no code:

from pylabrobot.shaking.backend import ShakerBackend
from pylabrobot.temperature_controlling.backend import TemperatureControllerBackend

class HeaterShakerBackend(ShakerBackend, TemperatureControllerBackend):
  """ Heater shaker backend: a union of ShakerBackend and TemperatureControllerBackend """



extremely based to see your rapid integrations! this is the valuable work that speeds up the time-to-working-protocol for new adopters of pylabrobot


I’m waiting until we can have a Kula Shaker integrated

why wait?


@CamilloMoschner I finally got my hands on a couple HHS’. Would you be interested in helping me get them running with PLR? It’s not clear exactly how to do so after your PR, or if I’m just having a firmware issue.

I’m also happy to help with resource definition and other things necessary to get them fully functional, as noted in the PR.


Hi @jonlaurent,

How many HHSs are you thinking of integrating?

Based on that number you have different options of how to integrate them. If you only have 2 then my PR will allow you to use them with PLR straight away.
This is because the PR integrated HHS and HHC machines that are plugged straight into the STAR(let).
There are only 2 “TCC”/RS232 ports on the STAR(let). Hence if you have 1 or 2 you can just plug them in, and then control their use through STAR(let) firmware commands.

If you have more then you need to control them through a separate controller. Hamilton sells their “Hamilton Shaker Box” (HSB) for this purpose.
We don’t have an HSB, so I started the integration work with direct plugin and control through the STAR(let). (Also, an HSB alone costs at least £2k in the UK… so I intended to go for Inheco machines in the future)

To answer your question: Yes, I am happy to help you with the integration in my free time. I am not an expert but I am sure we can figure this out :slight_smile:

1 Like

is it technically possible to plug an HHS directly into your computer / does it use RS-232? It might be possible to circumvent the HSB if it is just a multiplexer.

Yes, the HHS does use RS-232. But it uses it for power and communication and I am not sure about what the power requirements are and whether they can be provided directly from computer.

1 Like

Another topic related to the current one and part of this thread’s agenda (dev discussions on heaters, shakers and the like) as well:

Every machine requires two PLR definitions to make them usable, to my understanding:

  • a physical / Resource definition
  • a control software module (the backend)

We are currently discussing the backend, how to establish control of machines (in this case the HHS) using PLR.
Controlling up to two devices directly plugged into a STAR(let) is already possible.
Now we are trying to figure out how to make it possible to control more than 2 machines of this type.

In parallel, I have been making Resource definitions for the HHS and the HHC and would like to brainstorm with you guys the most sensible ways to integrate them with PLR.

My proposal

We just integrated the MFXCarrier and MFXModule concepts into PLR (PR#77).
Machines like the HHS and HHC could, physically speaking, all be members of the MFXModule superset.
The benefit of this approach is that one person has to generate the physical definition and everyone in the world can instantly use the “site” on top of the machine straight away.

The alternative would be that these machines could all be simply defined as a Resource class. But then any assignment of a child would have to be made explicitly using location=Coordinate(x,y,z) - every time anew when the deck is generated.
This leaves a lot of room for errors and I therefore recommend we generate these physical definitions using the MFXModule as the base class.

The physical definition for use of the HHS could therefore look like this:

################## 2. Machine modules ##################

def Hamilton_HS(name: str) -> MFXModule:
  """ Hamilton cat. no.: 199034
  Hamilton Heater Shaker with 3.0mm shaking orbit and flat
  bottom adapter (shaking speed: 100 -2400 rpm, temperature
  control: RT+5°C - 105°C, max. loading: 300mm)
  return MFXModule(
    size_z=184.1-8.0-100, # includes HHS' carrier_site_skirt_height=2.85mm
    # probe height - carrier_height - deck_height
    child_resource_location=Coordinate(9.4, 8.55, 184.1-8.0-100)
      # site_size_x=127.0,
      # site_size_y=86.0,

→ Note:
This definition is still very flexible. These devices were made to be screwed into a MFXCarrier, hence I believe making them members of the PLR superset of MFXModules makes sense to me, and I’d assume that the vast majority of the time this would be how they are used, i.e. screwed onto a MFXCarrier.
However, this definition still allows PLR users to define them anywhere:

  • maybe someone wants to screw them into a liquid_handler deck directly
  • maybe someone wants to just place the Hamilton_HS on a bench and programme an arm to place labware on them (think massive heater-shaker arrays)
    …the important thing is to not limit usability.

Please also provide feedback on the naming. I am not attached to “Hamilton_HS”, I just found it a bit more descriptive than “HHS” (and generally try to find a balance between avoiding acronyms as much as possible and not having to write out lengthy descriptions).
Please also voice your opinion on how to best connect the naming of…

  1. the PLR control software module (the backend)
  2. the naming of the physical definition of these smaller machines.

@jonlaurent, as you recently mentioned in a PM, there is currently no PLR documentation/tutorial on how to use the HHS and HHC besides the comments of PR#66 in which I integrated their control when plugged straight into the STAR(let).

Until we have written the tutorial, here is a simple code snippet that should allow you to use the HHS with PLR if your firmware is up to date:

# Initialise your machines
await lh.backend.initialize_hhs(1)
await lh.backend.initialize_hhs(2)

# Use them

# Set temperature
set_temperature = 37 # degree Celsius, of course
current_temperature = await lh.backend.get_temperature_at_hhs(1)
# Note: await lh.backend.get_temperature_at_hhs(1)
#        returns a dictionary: {'middle_T': 23.0, 'edge_T': 22.6} of the two HHS sensors
# so you might want to take the mean, min, max value of these two sensors
# or maybe just the middle or just the edge value, depends on application
current_temperature = np.mean(list(current_temperature.values()))
await lh.backend.start_temperature_control_at_hhs(1, 37)

# Wait for temperature to reach set_temperature
# (NB.: this does not mean it has stabilised at that temperature
# but just that it has reached that temperature once)
while current_temperature != set_temperature:
    current_temperature = await lh.backend.get_temperature_at_hhs(1)
    current_temperature = np.mean(list(current_temperature.values()))

# Shake control
await lh.backend.start_shaking_at_hhs(1, 500)
await lh.backend.stop_shaking_at_hhs(1)

# Stop temperature control, i.e. HHS will cool passively to room temperature
await lh.backend.stop_temperature_control_at_hhs(1)

The first integer always indicates the port number. How does this correlate to your physical positioning of your 2 HHS on your deck, i.e. is 1 the HHS at the front or the back?
This is up to you:

  • you can connect the front HHS to the RS-232 port labelled “TCC1” making the front HHS #1 or
  • you can connect the front HHS to the RS-232 port labelled “TCC2” making the front HHS #2.

Please let us know whether this works on your machine.

To be clear:
When we bought the HHS, a Hamilton application engineer installed ours, and I believe they performed a firmware upgrade.
There is a chance that this firmware upgrade is necessary for this 2-device-plugged-into-STAR mode of operation… another reason for me to move to Inheco thermoshakers → paying for firmware upgrades on all of our machines without knowing that they are necessary or avoid all these unknowns by simply buying a separate machine and control it with PLR.

I hope this helps you get started with your HHS machines.


Thanks @CamilloMoschner ! This worked perfectly, and both of my HHS seem to be functioning great.

I also got a couple Inheco coolers, so I’ll be working on getting those running next @rickwierenga.