The BELEKTRONIG benchtop temperature controllers of the BTC series are used to control heating or Peltier elements. For my thesis project i had to write a driver for the device and now i’m sharing it for whoever is using the same device and wants a ready driver to communicate with the device

Code

import pyvisa, time, logging

class PIDDriverV2():
    def __init__(self, base):
        self.logger = logging.getLogger('logger')
        try:
            rm = pyvisa.ResourceManager()
            self.ser = rm.open_resource(str(base.pid_port))
            self.ser.write_termination = "\r"
        except Exception as e:
            self.logger.critical("Failed to establish connection to Thermostat " + str(e))
            
        self.max_voltage = self.getRatedVoltage()
        self.output_min, self.output_max = self.getOutputLimits()
        self.primary_sensor = self.getPrimarySensor()
    
    def getRatedVoltage(self):
        """Reading of the rated voltage of power supply used for temperature control."""
        self.ser.write('U1')
        res = self.ser.read_bytes(2)
        value = int.from_bytes(res, "big", signed = True)
        value = round(value/10.0,0) # to V/°C
        return value

    def getOutputLimits(self):
        self.ser.write('G8') 
        res = self.ser.read_bytes(8)
        value = int.from_bytes(res[0:4], "big", signed = True)
        min = value / (2**16-1) * 100
        value = int.from_bytes(res[4:8], "big", signed = True)
        max = value / (2**16-1) * 100
        return min, max

    def getPrimarySensor(self):
        """Get the Primary active sensor"""
        self.ser.write('R7')
        res = self.ser.read_bytes(1)
        value = int.from_bytes(res, "big")
        return value

    def getVoltageLimits(self):
        self.ser.write('G8') 
        res = self.ser.read_bytes(8) 
        value = int.from_bytes(res[0:4], "big", signed = True)
        min = value / (2**16-1) * self.max_voltage
        value = int.from_bytes(res[4:8], "big", signed = True)
        max = value / (2**16-1) * self.max_voltage
        return min, max

    def getDeviceConfiguration(self):
        """ get the device configuration of the instrument """
        self.ser.write('N1')
        answer = self.ser.read_bytes(16)
        return answer   

    def getSerialNumber(self):
        """ get the serial number of the instrument """
        self.ser.write('N2')
        answer = self.ser.read_bytes(4)
        value = int.from_bytes(answer, "big")
        return value   

    def getFirmwareVersion(self):
        """ get the firmware version of the instrument """
        self.ser.write('N3')
        answer = self.ser.read_bytes(4)
        value  = int.from_bytes(answer, "big")
        return value     

    def getDeviceFeatures(self):
        """ get the device features of the instrument """
        self.ser.write('N5')
        answer = self.ser.read_bytes(4)
        value = int.from_bytes(answer, "big")
        bit_string = "{0:32b}".format(value)
        bit_list = [x == "1" for x in bit_string][::-1]
        return bit_list

    def getOutputVoltage(self):
        """reading of the output power of the control output."""
        self.ser.write("A1")
        res = self.ser.read_bytes(4)
        value = int.from_bytes(res, "big", signed = True)
        return value / (2**16-1) * self.max_voltage

    def getOutputPower(self):
        """reading of the output power of the control output. -100% to 100%"""
        self.ser.write("A1")
        res = self.ser.read_bytes(4)
        value = int.from_bytes(res, "big", signed = True)
        value = value / (2**16-1) * self.max_voltage
        return value

    def getControllerMode(self):
        """reading of the mode of operation of the control output."""
        self.ser.write("B1")
        res = self.ser.read_bytes(1)
        value = int.from_bytes(res, "big", signed = True)
        return value

    def getSetpointTemp(self):
        """reading of the setpoint temperature in °C"""
        self.ser.write("S1")
        time.sleep(1)
        res = self.ser.read_bytes(4)
        #print(res)
        return int.from_bytes(res, "big", signed = True) / 1000

    def getSetpointTempNoOverwrite(self):
        """reading of the setpoint temperature WITHOUT overwriting the internal memory. Reading of the actual value of the setpoint temperature of the active temperature ramp."""
        self.ser.write("S2")
        res = ''
        while not res:
            time.sleep(0.3)
            res = self.ser.read_bytes(4)
        return int.from_bytes(res, "big", signed = True) / 1000

    def getSensorTemp(self, sensor = 0):
        """Reading of the temperature of sensor input 1...5 in °C"""
        if sensor == 0:
            sensor = self.primary_sensor
        self.ser.write("T"+str(sensor))
        time.sleep(0.3)
        answer = self.ser.read_bytes(4)
        temperature = int.from_bytes(answer, "big", signed = True) / 1000
        return temperature

    def setControllerMode(self, mode):
        """ set the cooling/heating mode for a given integer 
            0 = read only
            1 = heat only
            2 = cool only
            3 = heat and cool
        """
        mode = int(mode)
        modeByte = mode.to_bytes(1, "big")
        self.ser.write('b1' + modeByte.decode('latin-1'))

    def setSetpointTemp(self, value):
        """setting of the setpoint temperature in °C, don't use if setting point will change frequently so there won't be damage to the internal memory"""
        cmdprefixBytes = str.encode("s1")
        value = int(float(value) * 1000)
        valueBytes = value.to_bytes(4, byteorder='big', signed = True)
        cmdBytes = cmdprefixBytes + valueBytes + str.encode("\x0D")
        self.ser.write_raw(cmdBytes)
        return self.ser.read_bytes(1)

    def setSetpointTempNoOverwrite(self, value):
        """setting of the setpoint temperature WITHOUT overwriting the internal memory. Reading of the actual value of the setpoint temperature of the active temperature ramp."""
        cmdprefixBytes = str.encode("s2")
        value = int(float(value) * 1000)
        valueBytes = value.to_bytes(4, byteorder='big', signed = True)
        cmdBytes = cmdprefixBytes + valueBytes + str.encode("\x0D")
        self.ser.write_raw(cmdBytes)
        return self.ser.read_bytes(1)