The thermostat is a laboratory device that is designed for temperature control applications with liquid in a bath tank. An external loop circuit can be connected to the pump connectors so that the temperature of the bath can remain constant.

As discussed previously the thermostats job is to bring both plates to a base temperature. The thermostats reports to the PC after the installation of the driver with the identifier “STMicroelectronics virtual COM port” in Device Manager and to operate the device there are a list of commands that can be send to the device using any serial connection with the device.

The IN commands purpose is to retrieve parameters the command structure is command + TR. The OUT commands purpose is to set parameters (only in remote control mode) the command structure is command + space + Parameter + TR.

In order to make the drivers for the device, the commands above are divided into smaller functions of getters and setters, with input validation, output validation and error translation implemented on the driver class level as it can been seen below so the driver can be reused in future development and automation of lab devices.

Code

import pyvisa, time, logging

class ThermostatDriver():
    def __init__(self, base):
        self.logger = logging.getLogger('logger')
        try:
            rm = pyvisa.ResourceManager()
            self.ser = rm.open_resource(str(base.thermostat_port))
        except Exception as e:
            self.logger.critical("Failed to establish connection
                                    to Thermostat " + str(e))

    def getStatus(self):
        """current operating status"""
        self.ser.write('STATUS')
        time.sleep(0.2)
        answer = self.ser.read_bytes(self.ser.bytes_in_buffer).decode('latin-1')
        if answer == "E01":
            data = (  "The device is being operated with no or too little bath
                    fluid or the level is below the minimum level."
                    "Top up the bath fluid. A hose has burst
                    (bath fluid level too low because it has been pumped out)."
                    "Replace the hose and top up the bath fluid." )
        if answer == "E05":
            data = (  "The cable for the working temperature
                        sensor has been interrupted or short-circuited." )
        if answer == "E06":
            data = (  "Defect of the working or excess temperature sensor."
                    "The working and excess temperature sensors report
                    a temperature difference of more than 20 K")
        if answer == "E14":
            data = (  "The cut-out value of the excessive temperature
                    protector is below the defined working temperature."
                    "Set the safety temperature to a higher value." )
        if answer == "E33":
            data = (  "The cable for the over temperature safety
                        sensor has been broken or short-circuited." )
        if answer == "E60" or "E63" or "E70" or "E72" or "E80":
            data = (  "Internal error. Contact JULABO Service Department." )
        if answer == "E61":
            data = (  "Connection error between Corio CD and refrigeration unit" )
        if answer == "E82":
            data = (  "Warning: Update error (incorrect hex file).
                        Contact JULABO Service Department." )
        if answer == "E83":
            data = (  "Warning: Excessive power consumption via USB
                        interface (<300 mA)." )
        if answer == "E108":
            data = (  "The self-locking alarm is still active. Switch off
                        the device at the main switch. Wait for approx.
                        4 seconds and then switch it on again." )
        if answer == "E116":
            data = (  "The cut-out value of the excessive temperature protector
                        is below the defined working temperature.
                        Set the safety temperature to a higher value" )
        if answer == "E118":
            data = (  "The internal AD converter is defective" )
        if answer == "E431":
            data = (  "Maximum compressor current exceeded" )
        if answer == "E1431":
            data = (  "Warning: No compressor current detected." )
        if answer == "E401":
            data = (  "Temperature sensor evaporator outlet defective (short circuit)." )
        if answer == "E402":
            data = (  "Temperature sensor evaporator outlet defective (break)." )
        if answer == "E413":
            data = (  "Evaporation pressure sensor defective (short circuit)." )
        if answer == "E414":
            data = (  "Evaporation pressure sensor defective (break)." )
        if answer == "E417":
            data = (  "Condensation pressure sensor
                        defective (short circuit)." )
        if answer == "E418":
            data = (  "Condensation pressure sensor defective (break)." )
        if answer == "E425" or "E426" or "E427" or "E432" or "E433":
            data = (  "Error in refrigeration system" )
        if answer == "E1427":
            data = (  "Warning: Error in refrigeration system" )
        if answer == "E431":
            data = (  "Maximum compressor current exceeded." )
        else:
            data = ( "Error Unkonwen" )

    def getVersion(self):
        """device name + voltage variant + software version"""
        self.ser.write('VERSION')
        time.sleep(0.2)
        data = ''
        while not data:
            data = self.ser.read_bytes(self.ser.bytes_in_buffer).decode('latin-1')
        return data

    def setTargetTemperature(self, value):
        """set setpoint"""
        self.ser.write(('OUT_SP_00 '+ str(value)))

    def getTargetTemperature(self):
        """query Setpoint"""
        self.ser.write('IN_SP_00')
        time.sleep(1)
        data = ''
        while not data:
            data = self.ser.read_bytes(self.ser.bytes_in_buffer).decode('latin-1')
        return data

    def getTemperatureValue(self):
        """query current temperature value"""
        self.ser.write('IN_PV_00')
        time.sleep(0.2)
        data = ''
        while not data:
            data = self.ser.read_bytes(self.ser.bytes_in_buffer).decode('latin-1')
            data = float(str.strip(data))
        return data

    def getVar(self):
        """query Variable"""
        self.ser.write('IN_PV_01')
        data = ''
        while not data:
            data = self.ser.read_bytes(self.ser.bytes_in_buffer).decode('latin-1')
        return data

    def getSafetySensor(self):
        """query Safety sensor"""
        self.ser.write('IN_PV_03')
        data = ''
        while not data:
            data = self.ser.read_bytes(self.ser.bytes_in_buffer).decode('latin-1')
        return data

    def getSafetyPotentiometer(self):
        """query Safety potentiometer"""
        self.ser.write('IN_PV_04')
        time.sleep(0.2)
        data = ''
        while not data:
            data = self.ser.read_bytes(self.ser.bytes_in_buffer).decode('latin-1')
        return data

    def getStartStop(self):
        """query Start/Stopp"""
        self.ser.write('IN_MODE_05')
        time.sleep(0.2)
        data = ''
        while not data:
            data = self.ser.read_bytes(self.ser.bytes_in_buffer).decode('latin-1')
        return data

    def sendSetpoint(self):
        """send setpoint"""
        self.ser.write('OUT_SP_01')

    def startDevice(self):
        self.ser.write('OUT_MODE_05 1')
        time.sleep(0.2)
        data = self.ser.read_bytes(self.ser.bytes_in_buffer).decode('latin-1')

    def stopDevice(self):
        self.ser.write('OUT_MODE_05 0')
        time.sleep(0.2)
        data = self.ser.read_bytes(self.ser.bytes_in_buffer).decode('latin-1')