Source code for PyLabware.devices.ika_rv10

"""PyLabware driver for IKA RV10 rotavap."""

from typing import Optional, Union
import serial

# Core imports
from .. import parsers as parser
from ..controllers import AbstractRotavap, in_simulation_device_returns
from ..exceptions import PLConnectionError
from ..models import LabDeviceCommands, ConnectionParameters


[docs] class RV10RotovapCommands(LabDeviceCommands): """Collection of command definitions for RV10 rotavap. """ # ########################## Constants ################################## # Default name. DEFAULT_NAME = "RV10Digital" STATUS_CODES = { "0": "Manual operation", "1": "Remote operation", "E01": "No rotation", "E02": "No communication with the heating bath" } # Heating mediums for the bath HEATING_MEDIUMS = { 0: "Oil", 1: "Water" } # ################### Control commands ################################### # Get device name GET_NAME = {"name": "IN_NAME", "reply": {"type": str, "parser": parser.slicer, "args": [None, 11]}} # Get software version GET_VERSION = {"name": "IN_SOFTWARE", "reply": {"type": str}} # The lift commands in the manual are wrong. The working command was found by Sebastian Steiner by e-mailing IKA. # Move lift up LIFT_UP = {"name": "OUT_SP_62 1"} # Move lift down LIFT_DOWN = {"name": "OUT_SP_63 1"} # Get rotation speed GET_SPEED = {"name": "IN_PV_4", "reply": {"type": int, "parser": parser.slicer, "args": [None, -2]}} # Get rotation speed setpoint GET_SPEED_SET = {"name": "IN_SP_4", "reply": {"type": int, "parser": parser.slicer, "args": [None, -2]}} # Set rotation speed SET_SPEED = {"name": "OUT_SP_4", "type": int, "check": {"min": 0, "max": 280}} # Start rotation START_ROTATION = {"name": "START_4"} # Stop rotation STOP_ROTATION = {"name": "STOP_4"} # Start interval mode START_INTERVAL = {"name": "START_60"} # Stop interval mode STOP_INTERVAL = {"name": "STOP_60"} # Start timer mode START_TIMER = {"name": "START_61"} # Stop timer mode STOP_TIMER = {"name": "STOP_61"} # Get bath temperature GET_TEMP = {"name": "IN_PV_2", "reply": {"type": float, "parser": parser.slicer, "args": [None, -2]}} # Get bath temperature setpoint GET_TEMP_SET = {"name": "IN_SP_2", "reply": {"type": float, "parser": parser.slicer, "args": [None, -2]}} # Set bath temperature SET_TEMP = {"name": "OUT_SP_2", "type": int, "check": {"min": 0, "max": 180}} # Get bath safety temperature GET_TEMP_SAFE = {"name": "IN_SP_3"} # Set bath safety temperature - doesn't seem to work. # SET_TEMP_SAFE = {"name":"OUT_SP_3", "type":int, "check":{"min":50, "max":190}} # Read heating medium type - doesn't seem to work # GET_BATH_MEDIUM = {"name":"IN_SP_74", "reply":{"type":int}} # Set heating bath medium type # SET_BATH_MEDIUM = {"name":"OUT_SP_74", "type":int, "check":{"values":HEATING_MEDIUMS}} # Start heating START_HEAT = {"name": "START_2"} # Stop heating STOP_HEAT = {"name": "STOP_2"} # ################### Configuration commands ############################# # Reset rotavap & switch back to local control mode RESET = {"name": "RESET"} # Get rotavap status GET_STATUS = {"name": "STATUS", "reply": {"type": str}} # Set timer mode duration, minutes SET_TIMER_DURATION = {"name": "OUT_SP_60", "type": int, "check": {"min": 1, "max": 199}} # Set interval mode (left-right rotation) cycle time, seconds SET_INTERVAL_TIME = {"name": "OUT_SP_61", "type": int, "check": {"min": 1, "max": 60}}
[docs] class RV10Rotovap(AbstractRotavap): """ This provides a Python class for the IKA RV10 rotavap based on the english section of the original operation manuals for the rotavap and the heating bath, 20000005206 RV10 bd_082018 and 20000017436 HB digital_092017, respectively. """ def __init__(self, device_name: str, connection_mode: str, address: Optional[str], port: Union[str, int]): """Default constructor. """ # Load commands from helper class self.cmd = RV10RotovapCommands # Connection settings connection_parameters: ConnectionParameters = {} connection_parameters["port"] = port connection_parameters["address"] = address connection_parameters["baudrate"] = 9600 connection_parameters["bytesize"] = serial.SEVENBITS connection_parameters["parity"] = serial.PARITY_EVEN super().__init__(device_name, connection_mode, connection_parameters) # Protocol settings self.command_terminator = "\r\n" self.reply_terminator = "\r\n" self.args_delimiter = " " # The rotavap activates heating/rotation upon just updating the setpoint # These internal variables are to track the state & make behavior more predictable. self._rotating = False self._speed_setpoint: int = 0 self._heating = False self._temperature_setpoint: float = 0
[docs] def initialize_device(self): """Performs reset and do a custom initialization sequence. """ self.send(self.cmd.RESET) # This is legacy initialization from PL1 # According to Sebastian, without it RV didn't enter remote control mode # TODO Check if it's actually needed self.start_temperature_regulation() self.stop_temperature_regulation() self.start_rotation() self.stop_rotation() self.start_task(interval=10, method=self.get_temperature)
[docs] @in_simulation_device_returns(RV10RotovapCommands.DEFAULT_NAME) def is_connected(self) -> bool: """Checks if device is connected. """ try: reply = self.send(self.cmd.GET_NAME) except PLConnectionError: return False return reply == self.cmd.DEFAULT_NAME
[docs] def is_idle(self) -> bool: """Checks if device is ready - no explicit method for that. """ if not self.is_connected(): return False return not (self._heating or self._rotating)
[docs] def get_status(self): """Not yet implemented. #TODO """ raise NotImplementedError
[docs] def check_errors(self): """Not yet implemented. #TODO """ raise NotImplementedError
[docs] def clear_errors(self): """Not yet implemented. #TODO """ raise NotImplementedError
[docs] def start(self): """Starts evaporation. """ self.start_rotation() self.lift_down() self.start_bath()
[docs] def stop(self): """Stops evaporation. """ self.stop_bath() self.lift_up() self.stop_rotation()
[docs] def start_bath(self): """Starts heating. """ self.send(self.cmd.START_HEAT) self._heating = True # This has to be done after the internal variable update # so that the actual device setting is updated self.set_temperature(self._temperature_setpoint)
[docs] def stop_bath(self): """Stops heating. """ self.send(self.cmd.STOP_HEAT) self._heating = False
[docs] def set_temperature(self, temperature: float, sensor: int = 0): """Sets the desired bath temperature. Args: temperature (float): Temperature setpoint in °C. sensor (int): Specify which temperature probe the setpoint applies to. This device has only an internal sensor. Thus, the sensor variable has no effect here. """ # If heating is not on, just update internal variable if not self._heating: # Check value against limits before updating self.check_value(self.cmd.SET_TEMP, temperature) self._temperature_setpoint = temperature else: self.send(self.cmd.SET_TEMP, temperature) self._temperature_setpoint = temperature
[docs] def get_temperature(self, sensor: int = 0) -> float: """Gets current bath temperature. Args: sensor (int): Specify which temperature probe the setpoint applies to. This device has only an internal sensor. Thus, the sensor variable has no effect here. """ return self.send(self.cmd.GET_TEMP)
[docs] def get_temperature_setpoint(self, sensor: int = 0) -> float: """Reads the current temperature setpoint. Args: sensor (int): Specify which temperature probe the setpoint applies to. This device has only an internal sensor. Thus, the sensor variable has no effect here. """ return self._temperature_setpoint
[docs] def start_rotation(self): """Starts rotation. """ self.send(self.cmd.START_ROTATION) self._rotating = True # This has to be done after the internal variable update # so that the actual device setting is updated self.set_speed(self._speed_setpoint)
[docs] def stop_rotation(self): """Stops rotation. """ self.send(self.cmd.STOP_ROTATION) self._rotating = False
[docs] def set_speed(self, speed: int): """Sets desired rotation speed. """ # If rotation is not on, just update internal variable if not self._rotating: # Check value against limits before updating self.check_value(self.cmd.SET_SPEED, speed) self._speed_setpoint = speed else: self.send(self.cmd.SET_SPEED, speed) self._speed_setpoint = speed
[docs] def get_speed(self) -> int: """Gets actual rotation speed. """ return self.send(self.cmd.GET_SPEED)
[docs] def get_speed_setpoint(self) -> int: """Gets current rotation speed setpoint. """ return self._speed_setpoint
[docs] def lift_up(self): """Move evaporation flask up. """ self.send(self.cmd.LIFT_UP)
[docs] def lift_down(self): """Move evaporation flask down. """ self.send(self.cmd.LIFT_DOWN)