Source code for labtronyx.drivers.Regatron.Source

"""
.. codeauthor:: Kevin Kennedy <protonyx@users.noreply.github.com>

Connecting to the Instrument
----------------------------

The Regatron sources can be outfitted with Ethernet-to-USB adapters for use
with the TopCon software. In order to connect to the source with an adapter
installed, you must install the drivers so that the device appears as a
local COM port to the operating system.
"""
import labtronyx

import struct


[docs]class d_TopCon(labtronyx.DriverBase): """ Driver for Regatron TopCon compatible Power Supplies """ author = 'KKENNEDY' version = '1.0' deviceType = 'Power Supply' compatibleInterfaces = ['Serial'] compatibleInstruments = { 'Regatron': ['GSS', 'TopCon'] } def open(self): # Configure the COM Port self.configure(baud_rate=38400, data_bits=8, parity='N', stop_bits=1) self._serial = self.getSerialNumber() self._fw = self.getFirmwareVersion() def close(self): pass def getProperties(self): return { 'deviceVendor': 'Regatron', 'deviceModel': 'TopCon Device', 'deviceSerial': self._serial, 'deviceFirmware': self._fw } def _sendFrame(self, frame): """ Send a frame Calculates the checksum and attaches a header to the outgoing data frame. Checksum is calculated by adding all data bytes, then mod by 0x100 First byte is a sync byte, it is always 0xA5 :returns: None """ fmt = 'BBB' total = 0 for bt in bytearray(frame): total += bt checksum = total % 0x100 header = struct.pack(fmt, 0xA5, len(frame), checksum) full = header + frame self.write(full) self.flush() def _recvFrame(self): """ Receive a frame Reads the header first to find out how many bytes to receive in the payload section :returns: byte string, payload section of frame """ header = self.read(3) sync, len, checksum = struct.unpack('BBB', header) # TODO Verify checksum? payload = self.read(len) return payload def _cmdReadMemoryWord(self, address): """ Read a memory word Talk ID: 0x10 Address is Little-endian (Least Significant Byte first) :returns: int16 """ frame = struct.pack('BBBB', 0x10, address&0xFF, (address>>8)&0xFF, (address>>16)&0xFF) self._sendFrame(frame) ret = self._recvFrame() try: talkid, status, datal, datah = struct.unpack('BBBB', ret) # Process returned data data = (datah<<8) | (datal) return data except: return 0 def _cmdWriteMemoryWord(self, address, data): """ Write a memory word Talk ID: 0x11 Address is Little-endian (Least Significant Byte first) Data is Little-endian :returns: bool, True is successful, False otherwise """ frame = struct.pack('BBBBBB', 0x11, address&0xFF, (address>>8)&0xFF, (address>>16)&0xFF, data&0xFF, (data>>8)&0xFF) self._sendFrame(frame) ret = self._recvFrame() try: talkid, status = struct.unpack('BB', ret) # TODO: Check status? return True except: return False
[docs] def powerOn(self): """ Switch on the power supply unit """ self._cmdWriteMemoryWord(0x005089, 1)
[docs] def powerOff(self): """ Switch off the power supply unit """ self._cmdWriteMemoryWord(0x005089, 0)
def getErrors(self): pass
[docs] def clearErrors(self): """ Clear all errors and warnings. In a multi-unit system the errors and warnings of all slave units will be cleared as well. """ self._cmdWriteMemoryWord(0x00508B, 1)
[docs] def selectUnit(self, index): """ Select a unit within the system for queries that require a unit to be selected. Possible values: * 0: Master * 1-63: Slave * 64: System The slave number (1 ... 63) required for the ModuleSelectIndex depends on the multi-unit operating mode and can be calculated with the values of the multi-unit ID selectors on the front panel according to the following formula: +==============================+===================+ | Multi-unit operating mode | ModuleSelectIndex | +==============================+===================+ | Parallel or series operation | (8 * AH) + AL | +------------------------------+-------------------+ | Multi-load operation | (16 * AH) + AL | +------------------------------+-------------------+ :param index: ModuleSelectIndex :type index: int """ self._cmdWriteMemoryWord(0x0050D0, index)
[docs] def getState(self): """ Returns the state of the selected unit. Select a unit using `selectUnit`. Possible values with Voltage OFF: * 'POWERUP' * 'READY' * 'ERROR' * 'STOP' Possible values with Voltage ON: * 'RUN' * 'WARN' :returns: str """ states = { 2: "POWERUP", 4: "READY", 12: "ERROR", 14: "STOP", 8: "RUN", 10: "WARN"} ret = self._cmdReadMemoryWord(0x00508C) return states.get(ret, "UNKNOWN")
[docs] def getMode(self): """ Returns the mode of the selected unit. Select a unit using `selectUnit`. Possible values: * 'cv': Constant Voltage * 'cc': Constant Current * 'cp': Constant Power * 'ulim': Usense limit active * 'plim': Psense limit active * 'cder': Current derating active :returns: str """ modes = { 0: 'cv', 1: 'cc', 2: 'cp', 3: 'ulim', 4: 'plim', 5: 'cder'} ret = self._cmdReadMemoryWord(0x0050B8) return modes.get(ret, 'UNKNOWN')
[docs] def getSerialNumber(self): """ Get the firmware serial number. :returns: str """ sn_h = self._cmdReadMemoryWord(0x005128) sn_l = self._cmdReadMemoryWord(0x005129) sn = (sn_h << 16) | sn_l ssn = str(sn) toadd = ord('A') - ord('0') a = chr(ord(ssn[4]) + toadd) b = chr(ord(ssn[5]) + toadd) return "{0:04d}-{1}{2}-{3:04d}".format(int(ssn[0:3]), a, b, int(ssn[6:]))
[docs] def getFirmwareVersion(self): """ Get the firmware version :returns: str """ high = self._cmdReadMemoryWord(0x007E01) mid = self._cmdReadMemoryWord(0x007E02) low = self._cmdReadMemoryWord(0x007E03) return "{0:d}.{1:d}.{2:d}".format(high, mid, low)
[docs] def getSystemVoltageRange(self): """ Get the minimum and maximum system voltage levels Units: V :returns: tuple (minimum, maximum) """ min_lvl = self._cmdReadMemoryWord(0x005112) max_lvl = self._cmdReadMemoryWord(0x00510B) return min_lvl, max_lvl
[docs] def getSystemCurrentRange(self): """ Get the minimum and maximum system current levels Units: A :returns: tuple (minimum, maximum) """ min_lvl = self._cmdReadMemoryWord(0x005113) max_lvl = self._cmdReadMemoryWord(0x00510C) return min_lvl, max_lvl
[docs] def getSystemPowerRange(self): """ Get the minimum and maximum system power levels Units: kW :returns: tuple (minimum, maximum) """ min_lvl = self._cmdReadMemoryWord(0x005114) max_lvl = self._cmdReadMemoryWord(0x00510D) return min_lvl, max_lvl
[docs] def getModuleVoltageRange(self): """ Get the minimum and maximum voltage levels for the currently selected module. Units: V :returns: tuple (minimum, maximum) """ min_lvl = self._cmdReadMemoryWord(0x00510F) max_lvl = self._cmdReadMemoryWord(0x005100) return min_lvl, max_lvl
[docs] def getModuleCurrentRange(self): """ Get the minimum and maximum current levels for the currently selected module. Units: A :returns: tuple (minimum, maximum) """ min_lvl = self._cmdReadMemoryWord(0x005110) max_lvl = self._cmdReadMemoryWord(0x005101) return min_lvl, max_lvl
[docs] def getModulePowerRange(self): """ Get the minimum and maximum power levels for the currently selected module. Units: kW :returns: tuple (minimum, maximum) """ min_lvl = self._cmdReadMemoryWord(0x005111) max_lvl = self._cmdReadMemoryWord(0x005102) return min_lvl, max_lvl
[docs] def setVoltage(self, voltage): """ Set the voltage limit. Note:: The master unit must be selected using `selectUnit(0)` :param voltage: Voltage setpoint :type voltage: float """ range = self.getSystemVoltageRange() min_lvl, max_lvl = map(float, range) set_voltage = int((voltage - min_lvl) * (4000.0) / (max_lvl - min_lvl)) self._cmdWriteMemoryWord(0x005080, set_voltage)
[docs] def setCurrent(self, neg, pos): """ Set the forward and reverse current flow limits. Note:: The master unit must be selected using `selectUnit(0)` :param neg: Reverse Current setpoint :type neg: float :param pos: Forward Current setpoint :type pos: float """ range = self.getSystemCurrentRange() min_lvl, max_lvl = map(float, range) set_current_neg = int((neg - min_lvl) * (-4000.0) / (0.0 - min_lvl) - 4000.0) set_current_pos = int((pos - 0.0) * (4000.0) / max_lvl) self._cmdWriteMemoryWord(0x30251D, set_current_neg) self._cmdWriteMemoryWord(0x005081, set_current_pos)
[docs] def setPower(self, neg, pos): """ Set the forward and reverse power flow limits. Note:: The master unit must be selected using `selectUnit(0)` :param neg: Reverse Power setpoint :type neg: float :param pos: Forward Power setpoint :type pos: float """ range = self.getSystemPowerRange() min_lvl, max_lvl = map(float, range) set_power_neg = int((neg - min_lvl) * (-4000.0) / (0.0 - min_lvl) - 4000.0) set_power_pos = int((pos - 0.0) * (4000.0) / (max_lvl)) self._cmdWriteMemoryWord(0x30251E, set_power_neg) self._cmdWriteMemoryWord(0x005082, set_power_pos)
[docs] def getTerminalVoltage(self): """ Get the actual system output voltage Note:: The module select should be set to system using: `selectUnit(64)` :returns: float """ voltage = self._cmdReadMemoryWord(0x005084) range = self.getSystemVoltageRange() min_lvl, max_lvl = map(float, range) return float((voltage) * (max_lvl - min_lvl) / (4000.0))
[docs] def getTerminalCurrent(self): """ Get the actual system output current Note:: The module select should be set to system using: `selectUnit(64)` :returns: float """ current = self._cmdReadMemoryWord(0x005085) range = self.getSystemCurrentRange() min_lvl, max_lvl = map(float, range) return float((current) * (max_lvl - min_lvl) / (4000.0))
#======================================================================= # # Register Address: 0x005085 # data = self._cmdReadMemoryWord(0x005085) # # # Value Range: 0 - 4000 (4000 = 125 A) # # The gain from the datasheet seems to be wrong # # This gain was generated by calibration # gain = 0.0399877 # 125.0/4000.0 # current = data * gain # return current #=======================================================================
[docs] def getTerminalPower(self): """ Get the actual system output power Note:: The module select should be set to system using: `selectUnit(64)` :returns: float """ power = self._cmdReadMemoryWord(0x005086) range = self.getSystemPowerRange() min_lvl, max_lvl = map(float, range) return float((power) * (max_lvl - min_lvl) / (4000.0))