__author__ = 'Markus Rosenstihl ' import re import serial import time import logging import os logfile = os.path.expanduser("~/.goniometer.log") logging.basicConfig(filename=logfile,level=logging.INFO) logger = logging.getLogger() logger.name = "goniometer" class goniometer: """ This class is meant to control the goniometer probe head from Marco Braun (AXYs) """ def __init__(self, dev="/dev/ttyUSB0", baudrate=38400, timeout=1, return_to_origin=True, loglevel=2, logangle=False): """ Upon creating an instance the goniometer will be initialised and the current angle defined as being 0. The goniometer log can be found in `~/.goniometer.log`. :param return_to_origin: Set to true if you want the stepper to return to the beginning when the class instance is deleted (for example with "del" ). Default: True :type return_to_origin: bool :param loglevel: This sets the log level 0=DEBUG,1=INFO,2=WARNING. Default: 2 :type loglevel: int :param logangle: Should a history of the angles be kept? The log can be accessed in the `_angle_history` variable. Default: False :type logangle: bool """ self.serial = serial.Serial(port=dev, baudrate=baudrate, timeout=timeout) logger.setLevel([logging.DEBUG,logging.INFO,logging.WARNING][loglevel]) self.serial.readlines() # empty serial buffer self.current_angle = 0.0 self._angle_history = [] self.initialise() self.return_to_origin = return_to_origin self.logangel = logangle def __del__(self): if self.return_to_origin: logging.info("Return to origin") self._send("g1") self._wait() self.serial.close() def _send(self, string_to_send): formatted_string = "%s>\r\n"%string_to_send logging.debug("send: %s"%(repr(formatted_string))) self.serial.write(formatted_string) def _recv(self): retstr = self.serial.readline().strip().replace("\x00","") search_result = re.search(r"<(\d+)>", retstr) if search_result != None: angle = float(search_result.group(1))/36.0 self.current_angle = angle if self.logangle: self._angle_history.append(angle) logging.debug("%.2f"%angle) return angle else: return None def _wait(self): logging.debug("Wait until rotation finnished (current: %.2f)"%(self.current_angle)) time.sleep(4) while True: retstr = self._recv() if not retstr: logging.debug("Rotation finnished (current: %.2f)"%(self.current_angle)) break def initialise(self): logging.info("Initialize goniometer") self._send("R000") self.current_angle = 0.0 def stop(self): """ Stops the current movement, not possible while doing :func:`step` and :func:`angle` """ logging.info("Stop goniometer") self._send("S456") def step(self, sample_rotation_degree=1.0): """ How many degrees should we step further. Negative values are not allowed, the stepper would return to 0. A ValueError exception is raised in this case. :param sample_rotation_degree: How many degrees to rotate. Default: 1 :type sample_rotation_degree: float """ if sample_rotation_degree <= 0: raise ValueError("This does not what you expect, would move to the start position whatever the value") logging.info("Step goniometer: %.2f"%sample_rotation_degree) direction = "g" if (sample_rotation_degree < 0) else "G" sample_rotation_degree = -1*sample_rotation_degree if sample_rotation_degree < 0 else sample_rotation_degree steps = int(sample_rotation_degree%360) self._send("%s%i"%(direction, steps )) self._wait() def angle(self, angle_degree): """ To what angle, in degrees, should we rotate. Mod 360 is calculated for `angle_degree`. If angle is smaller than current angle an exception will be raised. (From the underlying :func:`step` function) TODO: rotate either back or over to the wanted position. :param angle_degree: position in degree :type angle_degree: float """ angle_degree %= 360 delta_degree = angle_degree - self.current_angle logging.info("Rotate tp %.2f (from %.2f, i.e. delta=%.2f)"%(angle_degree, self.current_angle, delta_degree)) self.step(sample_rotation_degree=delta_degree)