From 9f4a3d6b5357eeb96fbe6c0843f074b510adaaa0 Mon Sep 17 00:00:00 2001 From: Markus Rosenstihl Date: Fri, 29 Apr 2016 19:12:21 +0000 Subject: [PATCH] Goniometer control for Marco Braun Probehead (AXYs) added --- src/tools/goniometer.py | 125 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 src/tools/goniometer.py diff --git a/src/tools/goniometer.py b/src/tools/goniometer.py new file mode 100644 index 0000000..80e6a6d --- /dev/null +++ b/src/tools/goniometer.py @@ -0,0 +1,125 @@ +__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("<(\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) + + + +