# ***************************************************************************** # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Module authors: # Paul M. Neves # ***************************************************************************** import time from frappy.core import Readable, Parameter, FloatRange, HasIO, StringIO, Property, IntRange,\ IDLE, BUSY, WARN, ERROR, Drivable, BoolType, Attached, StructOf class ACM1219IO(StringIO): """communication with ACM1219""" end_of_line = ('\r\n', '\r') # ('\n', '\r') ('\r\n', '\r') encoding = 'latin-1' # initial reply might not be ascii for a strange reason identification = [('*IDN?', r'.*,ACM1219,.*')] def checkHWIdent(self): for _ in range(3): time.sleep(0.5) try: self.communicate('*IDN?') break except Exception: pass super().checkHWIdent() class BothChannels(HasIO, Readable): """read both capacitance channels in multiplex mode""" # define the communication class for automatic creation of the IO module ioClass = ACM1219IO # modifying a property of inherited parameters (unit is propagated to the FloatRange datatype) value = Parameter('Capacitance 1 and 2, and VT', StructOf(C1=FloatRange(0, 21.096, unit='pF'), C2=FloatRange(0, 21.096, unit='pF'), VT=FloatRange(-1, 1000, unit='')), readonly=True) channels_enabled = Parameter('channels on or off', BoolType(), readonly=False) _ch_enabled = False def read_value(self): # using the inherited HasIO.communicate method to send a command and get the reply natempt = 0 maxAttempts = 5 while natempt < maxAttempts: try: reply = self.communicate(f'readMUC') # print(reply) reply = reply.split(',') C1 = float(reply[0]) C2 = float(reply[1]) VT = float(reply[2]) return {'C1': C1, 'C2': C2, 'VT': VT} except: '' natempt+=1 if natempt >= maxAttempts: print('Max attempt reached for reading arduino.') return self.value def read_status(self): # code = self.communicate(f'readStatus') # returns tons of data return IDLE, '' def read_channels_enabled(self): return self._ch_enabled def write_channels_enabled(self, channels_enabled): if channels_enabled: self.communicate(f'setCIN 1,0,00.0,00.0,0,00.0,00.0') self.communicate(f'setCIN 2,0,00.0,00.0,0,00.0,00.0') self._ch_enabled = True else: self.communicate(f'setCIN 0,0,00.0,00.0,0,00.0,00.0') self._ch_enabled = False return self.read_channels_enabled() class Displacement(Readable): # attached classes for capacitance and temperature cap = Attached() temp = Attached() # internal property to configure the channel channel = Property('the voltage channel for displacement capacitor', datatype=IntRange(1,2)) # modifying a property of inherited parameters (unit is propagated to the FloatRange datatype) value = Parameter('displacement', FloatRange(None, None, unit='um'), readonly=True) alpha290K = Parameter('capacitor constant at 290 K', FloatRange(None, None, unit='um pF'), readonly=False) d0 = Parameter('offset displacement', FloatRange(None, None, unit='um'), readonly=False) Cp = Parameter('parallel capacitance', FloatRange(None, None, unit='pF'), readonly=False) d0_curve = Parameter('calibration curve for offset displacement', StructOf(a=FloatRange(None, None, unit='um'), b=FloatRange(None, None, unit='um/K'), c=FloatRange(None, None, unit='um/K^2'), d=FloatRange(None, None, unit='um/K^3'), e=FloatRange(None, None, unit='um/K^4'),), readonly=False) def read_value(self): # get temperature and capacitance temp = self.temp.target cap = self.cap.value[f'C{self.channel}'] # calculate displacement from temperature and capacitance d0_T = self.d0_curve['a'] + self.d0_curve['b']*temp + self.d0_curve['c']*temp**2 + self.d0_curve['d']*temp**3 + self.d0_curve['e']*temp**4 disp = self.alpha290K / (cap - self.Cp) - self.d0 - d0_T return disp class Force(Readable): # attached classes for capacitance and temperature cap = Attached() temp = Attached() # internal property to configure the channel channel = Property('the voltage channel for force capacitor', datatype=IntRange(1,2)) # modifying a property of inherited parameters (unit is propagated to the FloatRange datatype) value = Parameter('force', FloatRange(None, None, unit='N'), readonly=True) alpha290K = Parameter('capacitor constant at 290 K', FloatRange(None, None, unit='N pF'), readonly=False) f0 = Parameter('offset force', FloatRange(None, None, unit='N'), readonly=False) Cp = Parameter('parallel capacitance', FloatRange(None, None, unit='pF'), readonly=False) f0_curve = Parameter('calibration curve for offset force', StructOf(a=FloatRange(None, None, unit='N'), b=FloatRange(None, None, unit='N/K'), c=FloatRange(None, None, unit='N/K^2'), d=FloatRange(None, None, unit='N/K^3'), e=FloatRange(None, None, unit='N/K^4'),), readonly=False) def read_value(self): # get temperature and capacitance temp = self.temp.target cap = self.cap.value[f'C{self.channel}'] # calculate force from temperature and capacitance alpha = self.alpha290K * (0.91 + 5e-5*temp + 9e-7*temp**2) f0_T = self.f0_curve['a'] + self.f0_curve['b']*temp + self.f0_curve['c']*temp**2 + self.f0_curve['d']*temp**3 + self.f0_curve['e']*temp**4 force = alpha / (cap - self.Cp) - self.f0 - f0_T return force class Stress(Readable): # attached classes for displacement force = Attached() # modifying a property of inherited parameters (unit is propagated to the FloatRange datatype) value = Parameter('stress', FloatRange(None, None, unit='GPa'), readonly=True) area = Parameter('cross sectional area of sample in mm^2', FloatRange(None, None, unit='mm^2'), readonly=False) def read_value(self): return self.force.value / self.area / 1000 class Strain(Readable): # attached classes for displacement displacement = Attached() # modifying a property of inherited parameters (unit is propagated to the FloatRange datatype) value = Parameter('strain', FloatRange(None, None, unit='m/m'), readonly=True) L = Parameter('length of sample in mm', FloatRange(None, None, unit='mm'), readonly=False) def read_value(self): return self.displacement.value / (1000*self.L) class YoungsModulus(Readable): # attached classes for displacement stress = Attached() strain = Attached() # modifying a property of inherited parameters (unit is propagated to the FloatRange datatype) value = Parameter('Young\'s modulus', FloatRange(None, None, unit='GPa'), readonly=True) def read_value(self): return self.stress.value / self.strain.value