diff --git a/AUTHORS b/AUTHORS index a6ad339..292c7fa 100644 --- a/AUTHORS +++ b/AUTHORS @@ -7,3 +7,4 @@ Martin Masip Pablo Jais Martin Caldarola Federico Barabas +Vasco Tenner diff --git a/lantz/action.py b/lantz/action.py index a86c74c..1308fab 100644 --- a/lantz/action.py +++ b/lantz/action.py @@ -20,7 +20,7 @@ from .processors import (Processor, FromQuantityProcessor, MapProcessor, RangeProcessor) -from .feat import MISSING +from .feat import MISSING, FeatActionUpdateModifiersMixing def _dget(adict, instance=MISSING): @@ -46,7 +46,7 @@ def _dset(adict, value, instance=MISSING): adict[instance] = value -class Action(object): +class ActionBase(object): """Wraps a Driver method with Lantz. Can be used as a decorator. Processors can registered for each arguments to modify their values before @@ -63,10 +63,11 @@ class Action(object): changed but only tested to belong to the container. :param units: `Quantity` or string that can be interpreted as units. :param procs: Other callables to be applied to input arguments. + :param log_output: Store the functions output in log """ - def __init__(self, func=None, *, values=None, units=None, limits=None, procs=None): + def __init__(self, func=None, *, values=None, units=None, limits=None, procs=None, log_output=True): #: instance: key: value self.modifiers = WeakKeyDictionary() @@ -76,6 +77,7 @@ def __init__(self, func=None, *, values=None, units=None, limits=None, procs=Non 'units': units, 'limits': limits, 'processors': procs} + self.log_output = log_output self.func = func self.args = () @@ -126,7 +128,10 @@ def call(self, instance, *args, **kwargs): tic = time.time() out = self.func(instance, *t_values) instance.timing.add(name, time.time() - tic) - instance.log_info('{} returned {}', name, out) + if self.log_output: + instance.log_info('{} returned {}', name, out) + else: + instance.log_info('{} returned a value of type {}', name, type(out)) return out except Exception as e: @@ -196,6 +201,10 @@ def rebuild(self, instance=MISSING, build_doc=False, modifiers=None, store=False return procs +class Action(ActionBase, FeatActionUpdateModifiersMixing): + pass + + class ActionProxy(object): """Proxy object for Actions that allows to store instance specific modifiers. diff --git a/lantz/driver.py b/lantz/driver.py index 803e9c6..10dd953 100644 --- a/lantz/driver.py +++ b/lantz/driver.py @@ -15,12 +15,14 @@ from functools import wraps from concurrent import futures from collections import defaultdict +import itertools from .utils.qt import MetaQObject, SuperQObject, QtCore from .feat import Feat, DictFeat, MISSING, FeatProxy from .action import Action, ActionProxy from .stats import RunningStats from .log import get_logger +from . import Q_ logger = get_logger('lantz.driver', False) @@ -42,6 +44,17 @@ def _merge_dicts(*args): return out +def unit_to_string(units): + return str(Q_(units).units) + +def unit_replace(unit, old, new): + unit = unit.split(' ') + for i, part in enumerate(unit): + if part == old: + unit[i] = new + + return ' '.join(unit) + class MetaSelf(type): """Metaclass for Self object @@ -466,6 +479,25 @@ def feats(self): def actions(self): return Proxy(self, self._lantz_actions, ActionProxy) + def update_units(self, old_units, units): + """Updates the units of all feats and actions and actions from self.units to units. + + E.g. mm/s -> radian.s + + :param old_units: string or units + :param units: string or units + :return: + """ + + old_units_str = unit_to_string(old_units) + units_str = unit_to_string(units) + + for featname, feat in itertools.chain(self.feats.items(), self.actions.items()): + old_feat_units_str = unit_to_string(feat.feat.modifiers[MISSING][MISSING]['units']) + new_feat_units = unit_replace(old_feat_units_str, old_units_str, units_str) + self.log_debug("Updating units of feat {} from {} to {}", featname, old_feat_units_str, new_feat_units) + feat.feat.change_units(units=new_feat_units) + def _solve_dependencies(dependencies, all_members=None): """Solve a dependency graph. diff --git a/lantz/drivers/alliedvision/__init__.py b/lantz/drivers/alliedvision/__init__.py index 1806fce..84eded0 100644 --- a/lantz/drivers/alliedvision/__init__.py +++ b/lantz/drivers/alliedvision/__init__.py @@ -1,3 +1,3 @@ -from .vimba import VimbaCam +from .vimba2 import VimbaCam, list_cameras -__all__ = ['VimbaCam'] +__all__ = ['VimbaCam', 'list_cameras'] diff --git a/lantz/drivers/alliedvision/tests/vimbatest.py b/lantz/drivers/alliedvision/tests/vimbatest.py new file mode 100644 index 0000000..1ea3937 --- /dev/null +++ b/lantz/drivers/alliedvision/tests/vimbatest.py @@ -0,0 +1,4 @@ +from lantz.drivers.alliedvision import list_cameras + +if __name__ == '__main__': + print(list_cameras()) \ No newline at end of file diff --git a/lantz/drivers/alliedvision/vimba.py b/lantz/drivers/alliedvision/vimba.py index 82b9267..4784799 100644 --- a/lantz/drivers/alliedvision/vimba.py +++ b/lantz/drivers/alliedvision/vimba.py @@ -3,40 +3,243 @@ lantz.drivers.alliedvision ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Implementation for a single vimba cam (could be modified to work with multiple camera if need be) + Implementation for a alliedvision camera via pymba and pylon - Requires pymba + Requires: + - pymba: https://github.com/morefigs/pymba + - vimba: https://www.alliedvision.com/en/products/software.html - Author: AlexBourassa and Kevin Miao - Date: 24/07/2017 + Log: + - create same API as lantz.drivers.basler + + + Author: Vasco Tenner + Date: 20181204 + + TODO: + - Test + - 12 bit packet readout + - Bandwith control + - Dynamically set available values for feats """ from lantz.driver import Driver from lantz import Feat, DictFeat, Action from pymba import Vimba import numpy as np +import threading +import time + + +beginner_controls = ['ExposureTimeAbs', 'GainRaw', 'Width', 'Height', + 'OffsetX', 'OffsetY'] +aliases = {'exposure_time': 'ExposureTimeAbs', + 'gain': 'GainRaw', + } + + +def todict(listitems): + 'Helper function to create dicts usable in feats' + d = {} + for item in listitems: + d.update({item: item}) + return d + + +def attach_dyn_propr(instance, prop_name, propr): + """Attach property proper to instance with name prop_name. + + Reference: + * https://stackoverflow.com/a/1355444/509706 + * https://stackoverflow.com/questions/48448074 + """ + class_name = instance.__class__.__name__ + 'C' + child_class = type(class_name, (instance.__class__,), {prop_name: propr}) + + instance.__class__ = child_class + + +def create_getter(p): + def tmpfunc(self): + return self.cam[p] + return tmpfunc + + +def create_setter(p): + def tmpfunc(self, val): + self.cam[p] = val + return tmpfunc + + +def list_cameras(): + with Vimba() as vimba: + # get system object + system = vimba.getSystem() + + # list available cameras (after enabling discovery for GigE cameras) + if system.GeVTLIsPresent: + system.runFeatureCommand("GeVDiscoveryAllOnce") + time.sleep(0.2) + cameraIds = vimba.getCameraIds() + for cameraId in cameraIds: + print(cameraId) class VimbaCam(Driver): + def __init__(self, camera=0, level='beginner', + *args, **kwargs): + """ + @params + :type camera_num: int, The camera device index: 0,1,.. + :type level: str, Level of controls to show ['beginner', 'expert'] + + Example: + import lantz + from lantz.drivers.alliedvision import Cam + import time + try: + lantzlog + except NameError: + lantzlog = lantz.log.log_to_screen(level=lantz.log.DEBUG) + + cam = Cam(camera='Basler acA4112-8gm (40006341)') + cam.initialize() + cam.exposure_time + cam.exposure_time = 3010 + cam.exposure_time + cam.grab_image() + print('Speedtest:') + nr = 10 + start = time.time() + for n in range(nr): + cam.grab_image() + duration = (time.time()-start)*1000*lantz.Q_('ms') + print('Read {} images in {}. Reading alone took {}. Framerate {}'.format(nr, + duration, duration - nr* cam.exposure_time, nr / duration.to('s'))) + cam.finalize() + """ + super().__init__(*args, **kwargs) + self.camera = camera + self.level = level + # Some actions cannot be performed while reading + self._grabbing_lock = threading.RLock() + def initialize(self): + """ + Params: + camera -- number in list of show_cameras or friendly_name + """ + self.vimba = Vimba() self.vimba.startup() - self.cam = self.vimba.getCamera(self.vimba.getCameraIds()[0]) - self.cam.openCamera() - self.cam.PixelFormat = 'Mono8' + cameras = self.vimba.getCameraIds() + self.log_debug('Available cameras are:' + str(cameras)) + + try: + if isinstance(self.camera, int): + cam = cameras[self.camera] + self.cam = self.vimba.getCamera(cam) + else: + self.cam = self.vimba.getCamera(self.camera) + except RuntimeError as err: + self.log_error(err) + raise RuntimeError(err) + self.frame = self.cam.getFrame() self.frame.announceFrame() self.cam.startCapture() - return + + self._dynamically_add_properties() + self._aliases() + + # get rid of Mono12Packed and give a log error: + fmt = self.pixel_format + if fmt == str('Mono12Packed'): + self.log_error('PixelFormat {} not supported. Using Mono12 ' + 'instead'.format(fmt)) + self.pixel_format = 'Mono12' + + # Go to full available speed + # cam.properties['DeviceLinkThroughputLimitMode'] = 'Off' def finalize(self): - self.vimba.shutdown() + self.cam.endCapture() + self.cam.revokeAllFrames() return + def _dynamically_add_properties(self): + """Add all properties available on driver as Feats""" + props = self.getFeatureNames() if self.level == 'expert' else beginner_controls + for p in props: + info = self.cam.getFeatureInfo(p) + range_ = self.cam.getFeatureRange(p) + limits = range_ if isinstance(tuple, range_) else None + values = range_ if isinstance(list, range_) else None + + feat = Feat(fget=create_getter(p), + fset=create_setter(p), + doc=info.description, + units=info.unit, + limits=limits, + values=values, + ) + feat.name = p + attach_dyn_propr(self, p, feat) + + def _aliases(self): + """Add easy to use aliases to strange internal pylon names + + Note that in the Logs, the original is renamed to the alias""" + for alias, orig in aliases.items(): + attach_dyn_propr(self, alias, self.feats[orig].feat) + + @Feat() + def info(self): + # We can still get information of the camera back + return 'Camera info of camera object:', self.cam.getInfo() # TODO TEST + + # Most properties are added automatically by _dynamically_add_properties + + @Feat(values=todict(['Mono8', 'Mono12', 'Mono12Packed'])) + def pixel_format(self): + fmt = self.cam['PixelFormat'] + if fmt == 'Mono12Packed': + self.log_error('PixelFormat {} not supported. Use Mono12 instead' + ''.format(fmt)) + return fmt + + @pixel_format.setter + def pixel_format(self, value): + if value == 'Mono12Packed': + self.log_error('PixelFormat {} not supported. Using Mono12 ' + 'instead'.format(value)) + value = 'Mono12' + self.cam['PixelFormat'] = value + + @Feat() + def properties(self): + """Dict with all properties supported by pylon dll driver""" + return self.cam.getFeatureNames() + @Action() - def getFrame(self): + def list_properties(self): + """List all properties and their values""" + for key in self.cam.getFeatureNames(): + try: + value = self.cam[key] + except IOError: + value = '' + + description = self.cam.getFeatureInfo(key) + range_ = self.cam.getFeatureRange(key) + print('{0} ({1}):\t{2}\t{3}'.format(key, description, value, range_)) + + @Action(log_output=False) + def grab_image(self): + """Record a single image from the camera""" + self.cam.AcquisitionMode = 'SingleFrame' try: self.frame.queueFrameCapture() success = True @@ -54,6 +257,153 @@ def getFrame(self): 'shape': (self.frame.height, self.frame.width, 1), } img = np.ndarray(**img_config) - return img[...,0] + return img[..., 0] else: return None + + @Action(log_output=False) + def grab_images(self, num=1): + # ΤΟDO see https://gist.github.com/npyoung/1c160c9eee91fd44c587 + raise NotImplemented() + with self._grabbing_lock: + img = self.cam.grab_images(num) + return img + + @Action(log_output=False) + def getFrame(self): + """Backward compatibility""" + return self.grab_image + + @Action() + def set_roi(self, height, width, yoffset, xoffset): + # Validation: + if width + xoffset > self.properties['WidthMax']: + self.log_error('Not setting ROI: Width + xoffset = {} exceeding ' + 'max width of camera {}.'.format(width + xoffset, + self.properties['WidthMax'])) + return + if height + yoffset > self.properties['HeightMax']: + self.log_error('Not setting ROI: Height + yoffset = {} exceeding ' + 'max height of camera {}.'.format(height + yoffset, + self.properties['HeightMax'])) + return + + # Offset should be multiple of 2: + xoffset -= xoffset % 2 + yoffset -= yoffset % 2 + + if height < 16: + self.log_error('Height {} too small, smaller than 16. Adjusting ' + 'to 16'.format(height)) + height = 16 + if width < 16: + self.log_error('Width {} too small, smaller than 16. Adjusting ' + 'to 16'.format(width)) + width = 16 + + with self._grabbing_lock: + # Order matters! + if self.OffsetY > yoffset: + self.Height = height + self.OffsetY = yoffset + else: + self.Height = height + self.OffsetY = yoffset + if self.OffsetX > xoffset: + self.OffsetX = xoffset + self.Width = width + else: + self.Width = width + self.OffsetX = xoffset + + @Action() + def reset_roi(self): + """Sets ROI to maximum camera size""" + self.set_roi(self.properties['HeightMax'], + self.properties['WidthMax'], + 0, + 0) + + # Helperfunctions for ROI settings + def limit_width(self, dx): + if dx > self.properties['WidthMax']: + dx = self.properties['WidthMax'] + elif dx < 16: + dx = 16 + return dx + + def limit_height(self, dy): + if dy > self.properties['HeightMax']: + dy = self.properties['HeightMax'] + elif dy < 16: + dy = 16 + return dy + + def limit_xoffset(self, xoffset, dx): + if xoffset < 0: + xoffset = 0 + if xoffset + dx > self.properties['WidthMax']: + xoffset = self.properties['WidthMax'] - dx + return xoffset + + def limit_yoffset(self, yoffset, dy): + if yoffset < 0: + yoffset = 0 + if yoffset + dy > self.properties['HeightMax']: + yoffset = self.properties['HeightMax'] - dy + return yoffset + + @Action() + def calc_roi(self, center=None, size=None, coords=None): + """Calculate the left bottom corner and the width and height + of a box with center (x,y) and size x [(x,y)]. Respects device + size""" + if center and size: + y, x = center + try: + dy, dx = size + except TypeError: + dx = dy = size + + # Make sizes never exceed camera sizes + dx = self.limit_width(dx) + dy = self.limit_width(dy) + + xoffset = x - dx // 2 + yoffset = y - dy // 2 + + xoffset = self.limit_xoffset(xoffset, dx) + yoffset = self.limit_yoffset(yoffset, dy) + + return dy, dx, yoffset, xoffset + + elif coords: + xoffset = int(coords[1][0]) + dx = int(coords[1][1] - xoffset) + + yoffset = int(coords[0][0]) + dy = int(coords[0][1] - yoffset) + + # print(dy,dx) + dx = self.limit_width(dx) + dy = self.limit_height(dy) + + # print(yoffset, xoffset) + xoffset = self.limit_xoffset(xoffset, dx) + yoffset = self.limit_yoffset(yoffset, dy) + + return dy, dx, yoffset, xoffset + + else: + raise ValueError('center&size or coords should be supplied') + + def calc_roi_from_rel_coords(self, relcoords): + """Calculate the new ROI from coordinates relative to the current + viewport""" + + coords = ((self.OffsetY + relcoords[0][0], + self.OffsetY + relcoords[0][1]), + (self.OffsetX + relcoords[1][0], + self.OffsetX + relcoords[1][1])) + # print('Rel_coords says new coords are', coords) + return self.calc_roi(coords=coords) \ No newline at end of file diff --git a/lantz/drivers/basler/basler.py b/lantz/drivers/basler/basler.py index a5d4bf8..972787b 100644 --- a/lantz/drivers/basler/basler.py +++ b/lantz/drivers/basler/basler.py @@ -3,78 +3,389 @@ lantz.drivers.basler ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Wraps Basler's pylon library using pypylon to provide lantz-style camera driver. + Implementation for a basler camera via pypylon and pylon - Author: Peter Mintun - Date: 9/21/2017 + Requires: + - pylon https://www.baslerweb.com/en/support/downloads/software-downloads/ + + Log: + - Dynamically add available feats + - Collect single and multiple images + - Set Gain and exposure time + - PyPylon https://github.com/dihm/PyPylon (tested with version: + f5b5f8dfb179af6c23340fe81a10bb75f2f467f7) + + + Author: Vasco Tenner + Date: 20171208 + + TODO: + - 12 bit packet readout + - Bandwith control + - Dynamically set available values for feats + - Set ROI """ from lantz.driver import Driver +# from lantz.foreign import LibraryDriver from lantz import Feat, DictFeat, Action +# import ctypes as ct +import pypylon +# import numpy as np +import threading -import numpy as np -import pypylon +beginner_controls = ['ExposureTimeAbs', 'GainRaw', 'Width', 'Height', + 'OffsetX', 'OffsetY'] +property_units = {'ExposureTimeAbs': 'us', } +aliases = {'exposure_time': 'ExposureTimeAbs', + 'gain': 'GainRaw', + } + + +def todict(listitems): + 'Helper function to create dicts usable in feats' + d = {} + for item in listitems: + d.update({item: item}) + return d + + +def attach_dyn_propr(instance, prop_name, propr): + """Attach property proper to instance with name prop_name. + + Reference: + * https://stackoverflow.com/a/1355444/509706 + * https://stackoverflow.com/questions/48448074 + """ + class_name = instance.__class__.__name__ + 'C' + child_class = type(class_name, (instance.__class__,), {prop_name: propr}) + + instance.__class__ = child_class + + +def create_getter(p): + def tmpfunc(self): + return self.cam.properties[p] + return tmpfunc + + +def create_setter(p): + def tmpfunc(self, val): + self.cam.properties[p] = val + return tmpfunc + class BaslerCam(Driver): + # LIBRARY_NAME = '/opt/pylon5/lib64/libpylonc.so' + + def __init__(self, camera=0, level='beginner', + *args, **kwargs): + """ + @params + :type camera_num: int, The camera device index: 0,1,.. + :type level: str, Level of controls to show ['beginner', 'expert'] + + Example: + import lantz + from lantz.drivers.basler import Cam + import time + try: + lantzlog + except NameError: + lantzlog = lantz.log.log_to_screen(level=lantz.log.DEBUG) + + cam = Cam(camera='Basler acA4112-8gm (40006341)') + cam.initialize() + cam.exposure_time + cam.exposure_time = 3010 + cam.exposure_time + next(cam.grab_images()) + cam.grab_image() + print('Speedtest:') + nr = 10 + start = time.time() + for n in cam.grab_images(nr): + n + duration = (time.time()-start)*1000*lantz.Q_('ms') + print('Read {} images in {}. Reading alone took {}. Framerate {}'.format(nr, + duration, duration - nr* cam.exposure_time, nr / duration.to('s'))) + cam.finalize() + """ + super().__init__(*args, **kwargs) + self.camera = camera + self.level = level + # Some actions cannot be performed while reading + self._grabbing_lock = threading.RLock() def initialize(self): - #print('Build against pylon library version:', pypylon.pylon_version.version) + ''' + Params: + camera -- number in list of show_cameras or friendly_name + ''' + + cameras = pypylon.factory.find_devices() + self.log_debug('Available cameras are:' + str(cameras)) - available_cameras = pypylon.factory.find_devices() - #print('Available cameras are', available_cameras) + try: + if isinstance(self.camera, int): + cam = cameras[self.camera] + self.cam = pypylon.factory.create_device(cam) + else: + try: + cam = [c for c in cameras + if c.friendly_name == self.camera][0] + self.cam = pypylon.factory.create_device(cam) + except IndexError: + self.log_error('Camera {} not found in cameras: {}' + ''.format(self.camera, cameras)) + return + except RuntimeError as err: + self.log_error(err) + raise RuntimeError(err) - # Grep the first one and create a camera for it - self.cam = pypylon.factory.create_device(available_cameras[-1]) + self.camera = cam.friendly_name + # First Open camera before anything is accessable self.cam.open() - self.cam.properties['PixelFormat'] = 'RGB8' - self.cam.properties['DeviceLinkThroughputLimitMode'] = 'Off' + self._dynamically_add_properties() + self._aliases() + + # get rid of Mono12Packed and give a log error: + fmt = self.pixel_format + if fmt == str('Mono12Packed'): + self.log_error('PixelFormat {} not supported. Using Mono12 ' + 'instead'.format(fmt)) + self.pixel_format = 'Mono12' + + # Go to full available speed + # cam.properties['DeviceLinkThroughputLimitMode'] = 'Off' + + def finalize(self): + self.cam.close() return - def list_properties(self): - """ - Utility function to pull all available options from the camera and - print them. - """ + def _dynamically_add_properties(self): + '''Add all properties available on driver as Feats''' + # What about units? + props = self.properties.keys() if self.level == 'expert' else beginner_controls + for p in props: + feat = Feat(fget=create_getter(p), + fset=create_setter(p), + doc=self.cam.properties.get_description(p), + units=property_units.get(p, None), + ) + feat.name = p + attach_dyn_propr(self, p, feat) + + def _aliases(self): + """Add easy to use aliases to strange internal pylon names + + Note that in the Logs, the original is renamed to the alias""" + for alias, orig in aliases.items(): + attach_dyn_propr(self, alias, self.feats[orig].feat) + + @Feat() + def info(self): + # We can still get information of the camera back + return 'Camera info of camera object:', self.cam.device_info + +# @Feat(units='us') +# def exposure_time(self): +# return self.cam.properties['ExposureTimeAbs'] +# +# @exposure_time.setter +# def exposure_time(self, time): +# self.cam.properties['ExposureTimeAbs'] = time +# +# @Feat() +# def gain(self): +# return self.cam.properties['GainRaw'] +# +# @gain.setter +# def gain(self, value): +# self.cam.properties['GainRaw'] = value + + @Feat(values=todict(['Mono8', 'Mono12', 'Mono12Packed'])) + def pixel_format(self): + fmt = self.cam.properties['PixelFormat'] + if fmt == 'Mono12Packed': + self.log_error('PixelFormat {} not supported. Use Mono12 instead' + ''.format(fmt)) + return fmt + + @pixel_format.setter + def pixel_format(self, value): + if value == 'Mono12Packed': + self.log_error('PixelFormat {} not supported. Using Mono12 ' + 'instead'.format(value)) + value = 'Mono12' + self.cam.properties['PixelFormat'] = value + + @Feat() + def properties(self): + 'Dict with all properties supported by pylon dll driver' + return self.cam.properties + @Action() + def list_properties(self): + 'List all properties and their values' for key in self.cam.properties.keys(): try: value = self.cam.properties[key] except IOError: value = '' - print('{0} ({1}):\t{2}'.format(key, self.cam.properties.get_description(key), value)) + description = self.cam.properties.get_description(key) + print('{0} ({1}):\t{2}'.format(key, description, value)) - def finalize(self): - self.cam.close() - return + @Action(log_output=False) + def grab_image(self): + """Read one single frame from camera""" + return next(self.cam.grab_images(1)) - @Action() + @Action(log_output=False) def getFrame(self): - try: - frame = self.cam.grab_images(1) - #self.next_frame = frame.next() - return frame - except: - return None + """Deprecated: backwards compatibility""" + return self.grab_image() + + @Action(log_output=False) + def grab_images(self, num=1): + with self._grabbing_lock: + img = self.cam.grab_images(num) + return img + + @Action() + def set_roi(self, height, width, yoffset, xoffset): + # Validation: + if width+xoffset > self.properties['WidthMax']: + self.log_error('Not setting ROI: Width + xoffset = {} exceeding ' + 'max width of camera {}.'.format(width+xoffset, + self.properties['WidthMax'])) + return + if height+yoffset > self.properties['HeightMax']: + self.log_error('Not setting ROI: Height + yoffset = {} exceeding ' + 'max height of camera {}.'.format(height+yoffset, + self.properties['HeightMax'])) + return + + # Offset should be multiple of 2: + xoffset -= xoffset % 2 + yoffset -= yoffset % 2 + + if height < 16: + self.log_error('Height {} too small, smaller than 16. Adjusting ' + 'to 16'.format(height)) + height = 16 + if width < 16: + self.log_error('Width {} too small, smaller than 16. Adjusting ' + 'to 16'.format(width)) + width = 16 + + with self._grabbing_lock: + # Order matters! + if self.OffsetY > yoffset: + self.OffsetY = yoffset + self.Height = height + else: + self.Height = height + self.OffsetY = yoffset + if self.OffsetX > xoffset: + self.OffsetX = xoffset + self.Width = width + else: + self.Width = width + self.OffsetX = xoffset + + @Action() + def reset_roi(self): + '''Sets ROI to maximum camera size''' + self.set_roi(self.properties['HeightMax'], + self.properties['WidthMax'], + 0, + 0) + + # Helperfunctions for ROI settings + def limit_width(self, dx): + if dx > self.properties['WidthMax']: + dx = self.properties['WidthMax'] + elif dx < 16: + dx = 16 + return dx + + def limit_height(self, dy): + if dy > self.properties['HeightMax']: + dy = self.properties['HeightMax'] + elif dy < 16: + dy = 16 + return dy + + def limit_xoffset(self, xoffset, dx): + if xoffset < 0: + xoffset = 0 + if xoffset + dx > self.properties['WidthMax']: + xoffset = self.properties['WidthMax'] - dx + return xoffset + + def limit_yoffset(self, yoffset, dy): + if yoffset < 0: + yoffset = 0 + if yoffset + dy > self.properties['HeightMax']: + yoffset = self.properties['HeightMax'] - dy + return yoffset + + @Action() + def calc_roi(self, center=None, size=None, coords=None): + '''Calculate the left bottom corner and the width and height + of a box with center (x,y) and size x [(x,y)]. Respects device + size''' + if center and size: + y, x = center + try: + dy, dx = size + except (TypeError): + dx = dy = size + + # Make sizes never exceed camera sizes + dx = self.limit_width(dx) + dy = self.limit_width(dy) + + xoffset = x - dx // 2 + yoffset = y - dy // 2 - return None + xoffset = self.limit_xoffset(xoffset, dx) + yoffset = self.limit_yoffset(yoffset, dy) + return dy, dx, yoffset, xoffset + elif coords: + xoffset = int(coords[1][0]) + dx = int(coords[1][1] - xoffset) -def test(): + yoffset = int(coords[0][0]) + dy = int(coords[0][1] - yoffset) - cam = BaslerCam() - cam.initialize() + # print(dy,dx) + dx = self.limit_width(dx) + dy = self.limit_height(dy) - for image in cam.getFrame(): + # print(yoffset, xoffset) + xoffset = self.limit_xoffset(xoffset, dx) + yoffset = self.limit_yoffset(yoffset, dy) - print(image) + return dy, dx, yoffset, xoffset - cam.finalize() + else: + raise ValueError('center&size or coords should be supplied') -if __name__ == '__main__': + def calc_roi_from_rel_coords(self, relcoords): + '''Calculate the new ROI from coordinates relative to the current + viewport''' - test() + coords = ((self.OffsetY + relcoords[0][0], + self.OffsetY + relcoords[0][1]), + (self.OffsetX + relcoords[1][0], + self.OffsetX + relcoords[1][1])) + # print('Rel_coords says new coords are', coords) + return self.calc_roi(coords=coords) diff --git a/lantz/drivers/basler/props.txt b/lantz/drivers/basler/props.txt new file mode 100644 index 0000000..dbb7d46 --- /dev/null +++ b/lantz/drivers/basler/props.txt @@ -0,0 +1,361 @@ +#This files contains the properties of a acA4112-8gm camera on 20180305 +Build against pylon library version: 5.0.11.build_10914 +Available cameras are [] +Camera info of camera object: +Bpp8 +SequenceEnable (Enables the sequencer.): False +SequenceCurrentSet (Current sequence set.): +SequenceConfigurationMode (Sets whether the sequencer can be configured.): Off +SequenceAsyncRestart (Allows to restart the sequence of sequence sets, starting with the sequence set that has the lowest index number. The restart is asynchronous to the camera's frame trigger. Only available in Auto and Controlled sequence advance mode.): +SequenceAsyncAdvance (Allows to advance from the current sequence set to the next one. The advance is asynchronous to the camera's frame trigger. Only available in Controlled sequence advance mode.): +SequenceSetTotalNumber (Total number of sequence sets in the sequence.): 2 +SequenceSetIndex (Index number of a sequence set.): 0 +SequenceSetStore (Stores the current sequence set. Storing the current sequence set will overwrite any already existing sequence set bearing the same index number. The sequence set is stored in the volatile memory and will therefore be lost if the camera is reset or if power is switched off.): +SequenceSetLoad (Loads an existing sequence set to make it the current sequence set.): +SequenceSetExecutions (Number of consecutive executions per sequence cycle for the selected sequence set. Only available in auto sequence advance mode.): 1 +SequenceAdvanceMode (Sets the sequence set advance mode.): Auto +SequenceControlSelector (Sets whether the sequence control source should be set for sequence advance or for sequence restart. Once this value has been set, a control source must be chosen using the SequenceControlSource parameter.): +SequenceControlSource (Sets the source for sequence control.): +SequenceAddressBitSelector (Sets which bit of the sequence set address can be controlled. Once a bit has been set, an input line can be set as the control source for this bit using the SequenceAddressBitSource parameter.): +SequenceAddressBitSource (Sets an input line as the control source for the currently selected sequence set address bit. The bit can be selected using the SequenceAddressBitSelector.): +GainAuto (Sets the operation mode of the gain auto function. The gain auto function automatically adjusts the gain within set limits until a target brightness value is reached.): Off +GainSelector (Sets the gain channel or tap to be adjusted. Once a gain channel or tap has been selected, all changes to the Gain parameter will be applied to the selected channel or tap.): All +GainRaw (Raw value of the selected gain control. The raw value is an integer value that sets the selected gain control in units specific to the camera.): 0 +BlackLevelSelector (Sets the black level channel or tap to be adjusted. Once a black level channel or tap has been selected, all changes to the BlackLevel parameter will be applied to the selected channel or tap.): All +BlackLevelRaw (Value of the selected black level control.): 0 +GammaEnable (Enables gamma correction.): False +GammaSelector (Sets the type of gamma to apply.): User +Gamma (Gamma correction value. Gamma correction lets you modify the brightness of the pixel values to account for a non-linearity in the human perception of brightness.): 1.0 +DigitalShift (Value set for digital shift. When the parameter is set to zero, digital shift will be disabled. When the parameter is set to 1, 2, 3, or 4, digital shift will be set to shift by 1, shift by 2, shift by 3, or shift by 4 respectively.): 0 +PixelFormat (Sets the format of the pixel data transmitted by the camera. The available pixel formats depend on the camera model and whether the camera is monochrome or color.): Mono8 +PixelSize (Returns the depth of the pixel values in the image (in bits per pixel). The value will always be coherent with the pixel format setting.): Bpp8 +PixelColorFilter (Returns the alignment of the camera's Bayer filter to the pixels in the acquired images.): None +PixelDynamicRangeMin (Minimum possible pixel value that could be transferred from the camera.): 0 +PixelDynamicRangeMax (Maximum possible pixel value that could be transferred from the camera.): 255 +ReverseX (Enables horizontal flipping of the image. The AOI is applied after the flipping.): False +ReverseY (Enables vertical flipping of the image. The AOI is applied after the flipping.): False +TestImageSelector (Sets the test image to display.): Off +TestImageResetAndHold (Holds all moving test images at their starting position. All test images will be displayed at their starting positions and will stay fixed.): False +SensorWidth (Width of the device's sensor in pixels.): 4112 +SensorHeight (Height of the device's sensor in pixels.): 3008 +WidthMax (Maximum allowed width of the image in pixels, taking into account any function that may limit the allowed width.): 4112 +HeightMax (Maximum allowed height of the image in pixels, taking into account any function that may limit the allowed height.): 3008 +LightSourceSelector (Sets the type of light source to be considered for matrix color transformation.): +BalanceWhiteReset (Allows returning to the color adjustment settings extant before the latest changes of the settings.): +BalanceWhiteAuto (Sets the operation mode of the balance white auto function.): +BalanceRatioSelector (Sets the color channel to be adjusted for manual white balance. Once a color intensity has been selected, all changes to the balance ratio parameter will be applied to the selected color intensity.): +BalanceRatioAbs (Value of the currently selected balance ratio channel or tap.): +BalanceRatioRaw (Value of the currently selected balance ratio control.): +ColorTransformationSelector (Sets the type of color transformation to be performed.): +ColorTransformationValueSelector (Sets the element to be entered in the color transformation matrix for custom color transformation. Note: Depending on the camera model, some elements in the color transformation matrix may be preset and can not be changed.): +ColorTransformationValue (Transformation value for the selected element in the color transformation matrix.): +ColorTransformationValueRaw (Raw transformation value for the selected element in the color transformation matrix.): +ColorTransformationMatrixFactor (Extent to which the selected light source will be considered in color matrix transformation.): +ColorTransformationMatrixFactorRaw (Extent to which the selected light source will be considered in color matrix transformation. If the value is set to 65536, the selected light source will be fully considered. If the value is set to 0, the selected light source will not be considered.): +ColorAdjustmentEnable (Enables color adjustment.): +ColorAdjustmentReset (Allows returning to the color adjustment settings extant before the latest changes of the settings.): +ColorAdjustmentSelector (Sets the color for color adjustment.): +ColorAdjustmentHue (Hue adjustment value for the currently selected color.): +ColorAdjustmentHueRaw (Adjustment of hue for the selected color.): +ColorAdjustmentSaturation (Saturation adjustment value for the currently selected color.): +ColorAdjustmentSaturationRaw (Adjustment of saturation for the selected color.): +DemosaicingMode (Sets the demosaicing mode.): +NoiseReductionAbs (Amount of noise reduction to apply. The higher the value, the less chroma noise will be visible in your images. However, too high values may result in image information loss. To enable this feature, the DemosaicingMode parameter must be set to BaslerPGI.): +NoiseReductionRaw (Amount of noise reduction to apply. The higher the value, the less chroma noise will be visible in your images. However, too high values may result in image information loss. To enable this feature, the DemosaicingMode parameter must be set to BaslerPGI.): +SharpnessEnhancementAbs (Amount of sharpening to apply. The higher the sharpness, the more distinct the image subject's contours will be. However, too high values may result in image information loss. To enable this feature, the DemosaicingMode parameter must be set to BaslerPGI.): +SharpnessEnhancementRaw (Amount of sharpening to apply. The higher the sharpness, the more distinct the image subject's contours will be. However, too high values may result in image information loss. To enable this feature, the DemosaicingMode parameter must be set to BaslerPGI.): +Width (Width of the area of interest in pixels.): 4096 +Height (Height of the area of interest in pixels.): 3000 +OffsetX (Horizontal offset from the left side of the sensor to the area of interest (in pixels).): 8 +OffsetY (Vertical offset from the top of the sensor to the area of interest (in pixels).): 4 +CenterX (Enables horizontal centering of the image.): False +CenterY (Enables vertical centering of the image.): False +BinningModeHorizontal (Sets the horizontal binning mode.): Summing +BinningHorizontalMode (Sets the binning horizontal mode.): Sum +BinningHorizontal (Number of adjacent horizontal pixels to be summed. Their charges will be summed and reported out of the camera as a single pixel.): 1 +BinningModeVertical (Sets the vertical binning mode.): Summing +BinningVerticalMode (Sets the binning vertical mode.): Sum +BinningVertical (Number of adjacent vertical pixels to be summed. Their charges will be summed and reported out of the camera as a single pixel.): 1 +DecimationHorizontal (Horizontal decimation factor. It specifies the extent of horizontal sub-sampling of the acquired frame, i.e. it defines how many pixel columns are left out of transmission. This has the net effect of reducing the horizontal resolution (width) of the image by the specified decimation factor. A value of 1 indicates that the camera performs no horizontal decimation.): +DecimationVertical (Vertical decimation factor. It specifies the extent of vertical sub-sampling of the acquired frame, i.e. it defines how many rows are left out of transmission. This has the net effect of reducing the vertical resolution (height) of the image by the specified decimation factor. A value of 1 indicates that the camera performs no vertical decimation.): +ScalingHorizontalAbs (Horizontal scaling factor.): +ScalingVerticalAbs (Vertical scaling factor): +EnableBurstAcquisition (Enables burst acquisition. When enabled, the maximum frame rate only depends on sensor timing and timing of the trigger sequence, and not on the image transfer rate out of the camera.): +AcquisitionMode (Sets the image acquisition mode.): Continuous +AcquisitionStart (Starts the acquisition of images. If the camera is set for single frame acquisition, it will start acquisition of one frame. If the camera is set for continuous frame acquisition, it will start continuous acquisition of frames.): +AcquisitionStop (Stops the acquisition of images if the camera is set for continuous image acquisition and acquisition has been started.): +AcquisitionFrameCount (Number of frames acquired in the multiframe acquisition mode.): 1 +TriggerSelector (Sets the trigger type to be configured. Once a trigger type has been set, all changes to the trigger settings will be applied to the selected trigger.): FrameStart +TriggerMode (Sets the mode for the currently selected trigger.): Off +TriggerSoftware (Generates a software trigger signal. The software trigger signal will be used if the TriggerSource parameter is set to Software.): +TriggerSource (Sets the signal source for the selected trigger.): Line1 +TriggerActivation (Sets the signal transition that activates the selected trigger.): RisingEdge +TriggerDelayAbs (Trigger delay time in microseconds. The delay is applied after the trigger reception and before effectively activating the trigger.): 0.0 +ExposureMode (Sets the exposure mode.): Timed +ExposureAuto (Sets the operation mode of the exposure auto function. The exposure auto function automatically adjusts the exposure time within set limits until a target brightness value is reached.): Off +ExposureTimeAbs (Exposure time of the camera in microseconds.): 3000.0 +ExposureTimeRaw (Raw exposure time of the camera. This value sets an integer that will be used as a multiplier for the exposure timebase. The actual exposure time equals the current ExposureTimeRaw setting multiplied with the current ExposureTimeBaseAbs setting.): 3000 +ReadoutTimeAbs (Sensor readout time given the current settings.): 104075.0 +ExposureOverlapTimeMode (Sets the exposure overlap time mode.): +ExposureOverlapTimeMaxAbs (Maximum overlap of the sensor exposure with sensor readout in TriggerWidth exposure mode (in microseconds).): +ExposureOverlapTimeMaxRaw (Maximum overlap time of the sensor exposure with sensor readout in TriggerWidth exposure mode (in raw units).): +ShutterMode (Sets the shutter mode.): Global +SensorReadoutMode (Sets the sensor readout mode.): +AcquisitionFrameRateEnable (Enables setting the camera's acquisition frame rate to a specified value.): False +AcquisitionFrameRateAbs (Acquisition frame rate of the camera in frames per second.): 8.0 +ResultingFramePeriodAbs (Minimum allowed frame acquisition period (in microseconds) given the current settings for the area of interest, exposure time, and bandwidth.): 115056.0 +ResultingFrameRateAbs (Maximum allowed frame acquisition rate given the current camera settings (in frames per second).): 8.691419830343484 +AcquisitionStatusSelector (Sets the acquisition status to be checked. Once a status has been set, the status can be checked by reading the AcquisitionStatus parameter value.): FrameTriggerWait +AcquisitionStatus (Indicates the status (true or false) of the currently selected acquisition signal. The acquisition signal can be selected using AcquisitionStatusSelector.): False +SyncFreeRunTimerEnable (Enables the synchronous free run mode. When enabled, the camera will generate all required frame start or line start trigger signals internally, and you do not need to apply frame start or line start trigger signals to the camera.): False +SyncFreeRunTimerStartTimeLow (Low 32 bits of the synchronous free run trigger start time.): 0 +SyncFreeRunTimerStartTimeHigh (High 32 bits of the synchronous free run trigger start time.): 0 +SyncFreeRunTimerTriggerRateAbs (Synchronous free run trigger rate.): 10.0 +SyncFreeRunTimerUpdate (Activates changed settings for the synchronous free run.): +TLParamsLocked (Indicates whether a live grab is under way.): 0 +LineSelector (Sets the I/O line to be configured. Once a line has been set, all changes to the line settings will be applied to the selected line.): Line1 +LineMode (Sets the mode for the selected line. This controls whether the physical line is used to input or output a signal.): Input +LineLogic (Returns the line logic of the currently selected line.): Positive +LineFormat (Returns the electrical configuration of the currently selected line.): OptoCoupled +LineSource (Sets the source signal for the currently selected line. The currently selected line must be an output line.): +LineInverter (Enables the signal inverter function for the currently selected input or output line.): False +LineTermination (Enables the termination resistor of the selected input line.): +LineDebouncerTimeAbs (Value of the selected line debouncer time in microseconds.): 0.0 +LineDebouncerTimeRaw (Raw value of the selected line debouncer time.): 0 +MinOutPulseWidthRaw (Raw value for the minimum signal width of a signal that is received from the frequency converter or from the shaft encoder module and that is associated with a digital output line.): +MinOutPulseWidthAbs (Value for the minimum signal width of an output signal (in microseconds) .): +LineStatus (Indicates the current logical state of the selected line.): False +LineStatusAll (A single bit field indicating the current logical state of all available line signals at time of polling.): 6 +UserOutputSelector (Sets the user settable output signal to be configured. Once a user settable output signal has been set, all changes to the user settable output signal settings will be applied to the selected user settable output signal.): UserOutput1 +UserOutputValue (Enables the selected user settable output line.): False +UserOutputValueAll (A single bit field that sets the state of all user settable output signals in one access): 0 +SyncUserOutputSelector (Sets the user settable synchronous output signal to be configured.): SyncUserOutput1 +SyncUserOutputValue (Enables the selected user settable synchronous output line.): False +SyncUserOutputValueAll (A single bit field that sets the state of all user settable synchronous output signals in one access.): 0 +TimerDelayTimebaseAbs (Time base (in microseconds) that is used when a timer delay is set using the TimerDelayRaw parameter.): 1.0 +TimerDurationTimebaseAbs (Time base (in microseconds) that is used when a timer duration is set using the TimerDurationRaw parameter.): 1.0 +TimerSelector (Sets the timer to be configured. Once a timer has been set, all changes to the timer settings will be applied to the selected timer.): Timer1 +TimerDelayAbs (Delay of the currently selected timer in microseconds.): 0.0 +TimerDelayRaw (Raw delay for the selected timer. This value sets an integer that will be used as a multiplier for the timer delay timebase. The actual delay time equals the current TimerDelayRaw setting multiplied with the current TimerDelayTimeBaseAbs setting.): 0 +TimerDurationAbs (Duration of the currently selected timer in microseconds.): 10.0 +TimerDurationRaw (Raw duration for the selected timer. This value sets an integer that will be used as a multiplier for the timer duration timebase. The actual duration time equals the current TimerDurationRaw setting multiplied with the current TimerDurationTimeBaseAbs setting.): 10 +TimerTriggerSource (Sets the internal camera signal used to trigger the selected timer.): ExposureStart +TimerTriggerActivation (Sets the type of signal transition that will start the timer.): RisingEdge +CounterSelector (Sets the counter to be configured. Once a counter has been set, all changes to the counter settings will be applied to this counter.): Counter1 +CounterEventSource (Sets the event that increments the currently selected counter.): FrameTrigger +CounterResetSource (Sets the source signal that can reset the currently selected counter.): Off +CounterReset (Immediately resets the selected counter. The counter starts counting immediately after the reset.): +LUTSelector (Sets the lookup table (LUT) to be configured. Once a LUT has been selected, all changes to the LUT settings will be applied to the selected LUT.): Luminance +LUTEnable (Enables the selected lookup table (LUT).): False +LUTIndex (Index of the LUT element to access.): 0 +LUTValue (Value of the LUT element at the LUT index position.): 0 +LUTValueAll (A single register that lets you access all LUT coefficients without the need to repeatedly use the LUTIndex parameter.): 0x00000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000580000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000006800000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000880000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000009800000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000b800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000d800000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000e800000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000000f80000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010800000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000118000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001280000000000000000000000000000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000013800000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000148000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000001580000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000016800000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000178000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001880000000000000000000000000000000000000000000000000000000000000190000000000000000000000000000000000000000000000000000000000000019800000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001a800000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b800000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000001c800000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000000000001d800000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001e800000000000000000000000000000000000000000000000000000000000001f000000000000000000000000000000000000000000000000000000000000001f80000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020800000000000000000000000000000000000000000000000000000000000002100000000000000000000000000000000000000000000000000000000000000218000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002280000000000000000000000000000000000000000000000000000000000000230000000000000000000000000000000000000000000000000000000000000023800000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000248000000000000000000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000000000000000002580000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000026800000000000000000000000000000000000000000000000000000000000002700000000000000000000000000000000000000000000000000000000000000278000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000002880000000000000000000000000000000000000000000000000000000000000290000000000000000000000000000000000000000000000000000000000000029800000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002a800000000000000000000000000000000000000000000000000000000000002b000000000000000000000000000000000000000000000000000000000000002b800000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002c800000000000000000000000000000000000000000000000000000000000002d000000000000000000000000000000000000000000000000000000000000002d800000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000002e800000000000000000000000000000000000000000000000000000000000002f000000000000000000000000000000000000000000000000000000000000002f80000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000030800000000000000000000000000000000000000000000000000000000000003100000000000000000000000000000000000000000000000000000000000000318000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003280000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000033800000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000348000000000000000000000000000000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000000000003580000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000036800000000000000000000000000000000000000000000000000000000000003700000000000000000000000000000000000000000000000000000000000000378000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000003880000000000000000000000000000000000000000000000000000000000000390000000000000000000000000000000000000000000000000000000000000039800000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003a800000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003b800000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003c800000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003d800000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000003f000000000000000000000000000000000000000000000000000000000000003f80000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040800000000000000000000000000000000000000000000000000000000000004100000000000000000000000000000000000000000000000000000000000000418000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000004280000000000000000000000000000000000000000000000000000000000000430000000000000000000000000000000000000000000000000000000000000043800000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000448000000000000000000000000000000000000000000000000000000000000045000000000000000000000000000000000000000000000000000000000000004580000000000000000000000000000000000000000000000000000000000000460000000000000000000000000000000000000000000000000000000000000046800000000000000000000000000000000000000000000000000000000000004700000000000000000000000000000000000000000000000000000000000000478000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000004880000000000000000000000000000000000000000000000000000000000000490000000000000000000000000000000000000000000000000000000000000049800000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000004a800000000000000000000000000000000000000000000000000000000000004b000000000000000000000000000000000000000000000000000000000000004b800000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000004c800000000000000000000000000000000000000000000000000000000000004d000000000000000000000000000000000000000000000000000000000000004d800000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000004e800000000000000000000000000000000000000000000000000000000000004f000000000000000000000000000000000000000000000000000000000000004f80000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000050800000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000518000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000005280000000000000000000000000000000000000000000000000000000000000530000000000000000000000000000000000000000000000000000000000000053800000000000000000000000000000000000000000000000000000000000005400000000000000000000000000000000000000000000000000000000000000548000000000000000000000000000000000000000000000000000000000000055000000000000000000000000000000000000000000000000000000000000005580000000000000000000000000000000000000000000000000000000000000560000000000000000000000000000000000000000000000000000000000000056800000000000000000000000000000000000000000000000000000000000005700000000000000000000000000000000000000000000000000000000000000578000000000000000000000000000000000000000000000000000000000000058000000000000000000000000000000000000000000000000000000000000005880000000000000000000000000000000000000000000000000000000000000590000000000000000000000000000000000000000000000000000000000000059800000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000000000000000005a800000000000000000000000000000000000000000000000000000000000005b000000000000000000000000000000000000000000000000000000000000005b800000000000000000000000000000000000000000000000000000000000005c000000000000000000000000000000000000000000000000000000000000005c800000000000000000000000000000000000000000000000000000000000005d000000000000000000000000000000000000000000000000000000000000005d800000000000000000000000000000000000000000000000000000000000005e000000000000000000000000000000000000000000000000000000000000005e800000000000000000000000000000000000000000000000000000000000005f000000000000000000000000000000000000000000000000000000000000005f80000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000060800000000000000000000000000000000000000000000000000000000000006100000000000000000000000000000000000000000000000000000000000000618000000000000000000000000000000000000000000000000000000000000062000000000000000000000000000000000000000000000000000000000000006280000000000000000000000000000000000000000000000000000000000000630000000000000000000000000000000000000000000000000000000000000063800000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000648000000000000000000000000000000000000000000000000000000000000065000000000000000000000000000000000000000000000000000000000000006580000000000000000000000000000000000000000000000000000000000000660000000000000000000000000000000000000000000000000000000000000066800000000000000000000000000000000000000000000000000000000000006700000000000000000000000000000000000000000000000000000000000000678000000000000000000000000000000000000000000000000000000000000068000000000000000000000000000000000000000000000000000000000000006880000000000000000000000000000000000000000000000000000000000000690000000000000000000000000000000000000000000000000000000000000069800000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000006a800000000000000000000000000000000000000000000000000000000000006b000000000000000000000000000000000000000000000000000000000000006b800000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000006c800000000000000000000000000000000000000000000000000000000000006d000000000000000000000000000000000000000000000000000000000000006d800000000000000000000000000000000000000000000000000000000000006e000000000000000000000000000000000000000000000000000000000000006e800000000000000000000000000000000000000000000000000000000000006f000000000000000000000000000000000000000000000000000000000000006f80000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000070800000000000000000000000000000000000000000000000000000000000007100000000000000000000000000000000000000000000000000000000000000718000000000000000000000000000000000000000000000000000000000000072000000000000000000000000000000000000000000000000000000000000007280000000000000000000000000000000000000000000000000000000000000730000000000000000000000000000000000000000000000000000000000000073800000000000000000000000000000000000000000000000000000000000007400000000000000000000000000000000000000000000000000000000000000748000000000000000000000000000000000000000000000000000000000000075000000000000000000000000000000000000000000000000000000000000007580000000000000000000000000000000000000000000000000000000000000760000000000000000000000000000000000000000000000000000000000000076800000000000000000000000000000000000000000000000000000000000007700000000000000000000000000000000000000000000000000000000000000778000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000007880000000000000000000000000000000000000000000000000000000000000790000000000000000000000000000000000000000000000000000000000000079800000000000000000000000000000000000000000000000000000000000007a000000000000000000000000000000000000000000000000000000000000007a800000000000000000000000000000000000000000000000000000000000007b000000000000000000000000000000000000000000000000000000000000007b800000000000000000000000000000000000000000000000000000000000007c000000000000000000000000000000000000000000000000000000000000007c800000000000000000000000000000000000000000000000000000000000007d000000000000000000000000000000000000000000000000000000000000007d800000000000000000000000000000000000000000000000000000000000007e000000000000000000000000000000000000000000000000000000000000007e800000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000007f80000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080800000000000000000000000000000000000000000000000000000000000008100000000000000000000000000000000000000000000000000000000000000818000000000000000000000000000000000000000000000000000000000000082000000000000000000000000000000000000000000000000000000000000008280000000000000000000000000000000000000000000000000000000000000830000000000000000000000000000000000000000000000000000000000000083800000000000000000000000000000000000000000000000000000000000008400000000000000000000000000000000000000000000000000000000000000848000000000000000000000000000000000000000000000000000000000000085000000000000000000000000000000000000000000000000000000000000008580000000000000000000000000000000000000000000000000000000000000860000000000000000000000000000000000000000000000000000000000000086800000000000000000000000000000000000000000000000000000000000008700000000000000000000000000000000000000000000000000000000000000878000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000000000000000000000000000008880000000000000000000000000000000000000000000000000000000000000890000000000000000000000000000000000000000000000000000000000000089800000000000000000000000000000000000000000000000000000000000008a000000000000000000000000000000000000000000000000000000000000008a800000000000000000000000000000000000000000000000000000000000008b000000000000000000000000000000000000000000000000000000000000008b800000000000000000000000000000000000000000000000000000000000008c000000000000000000000000000000000000000000000000000000000000008c800000000000000000000000000000000000000000000000000000000000008d000000000000000000000000000000000000000000000000000000000000008d800000000000000000000000000000000000000000000000000000000000008e000000000000000000000000000000000000000000000000000000000000008e800000000000000000000000000000000000000000000000000000000000008f000000000000000000000000000000000000000000000000000000000000008f80000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000090800000000000000000000000000000000000000000000000000000000000009100000000000000000000000000000000000000000000000000000000000000918000000000000000000000000000000000000000000000000000000000000092000000000000000000000000000000000000000000000000000000000000009280000000000000000000000000000000000000000000000000000000000000930000000000000000000000000000000000000000000000000000000000000093800000000000000000000000000000000000000000000000000000000000009400000000000000000000000000000000000000000000000000000000000000948000000000000000000000000000000000000000000000000000000000000095000000000000000000000000000000000000000000000000000000000000009580000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000000096800000000000000000000000000000000000000000000000000000000000009700000000000000000000000000000000000000000000000000000000000000978000000000000000000000000000000000000000000000000000000000000098000000000000000000000000000000000000000000000000000000000000009880000000000000000000000000000000000000000000000000000000000000990000000000000000000000000000000000000000000000000000000000000099800000000000000000000000000000000000000000000000000000000000009a000000000000000000000000000000000000000000000000000000000000009a800000000000000000000000000000000000000000000000000000000000009b000000000000000000000000000000000000000000000000000000000000009b800000000000000000000000000000000000000000000000000000000000009c000000000000000000000000000000000000000000000000000000000000009c800000000000000000000000000000000000000000000000000000000000009d000000000000000000000000000000000000000000000000000000000000009d800000000000000000000000000000000000000000000000000000000000009e000000000000000000000000000000000000000000000000000000000000009e800000000000000000000000000000000000000000000000000000000000009f000000000000000000000000000000000000000000000000000000000000009f80000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000a080000000000000000000000000000000000000000000000000000000000000a100000000000000000000000000000000000000000000000000000000000000a180000000000000000000000000000000000000000000000000000000000000a200000000000000000000000000000000000000000000000000000000000000a280000000000000000000000000000000000000000000000000000000000000a300000000000000000000000000000000000000000000000000000000000000a380000000000000000000000000000000000000000000000000000000000000a400000000000000000000000000000000000000000000000000000000000000a480000000000000000000000000000000000000000000000000000000000000a500000000000000000000000000000000000000000000000000000000000000a580000000000000000000000000000000000000000000000000000000000000a600000000000000000000000000000000000000000000000000000000000000a680000000000000000000000000000000000000000000000000000000000000a700000000000000000000000000000000000000000000000000000000000000a780000000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000000000000000a880000000000000000000000000000000000000000000000000000000000000a900000000000000000000000000000000000000000000000000000000000000a980000000000000000000000000000000000000000000000000000000000000aa00000000000000000000000000000000000000000000000000000000000000aa80000000000000000000000000000000000000000000000000000000000000ab00000000000000000000000000000000000000000000000000000000000000ab80000000000000000000000000000000000000000000000000000000000000ac00000000000000000000000000000000000000000000000000000000000000ac80000000000000000000000000000000000000000000000000000000000000ad00000000000000000000000000000000000000000000000000000000000000ad80000000000000000000000000000000000000000000000000000000000000ae00000000000000000000000000000000000000000000000000000000000000ae80000000000000000000000000000000000000000000000000000000000000af00000000000000000000000000000000000000000000000000000000000000af80000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000b080000000000000000000000000000000000000000000000000000000000000b100000000000000000000000000000000000000000000000000000000000000b180000000000000000000000000000000000000000000000000000000000000b200000000000000000000000000000000000000000000000000000000000000b280000000000000000000000000000000000000000000000000000000000000b300000000000000000000000000000000000000000000000000000000000000b380000000000000000000000000000000000000000000000000000000000000b400000000000000000000000000000000000000000000000000000000000000b480000000000000000000000000000000000000000000000000000000000000b500000000000000000000000000000000000000000000000000000000000000b580000000000000000000000000000000000000000000000000000000000000b600000000000000000000000000000000000000000000000000000000000000b680000000000000000000000000000000000000000000000000000000000000b700000000000000000000000000000000000000000000000000000000000000b780000000000000000000000000000000000000000000000000000000000000b800000000000000000000000000000000000000000000000000000000000000b880000000000000000000000000000000000000000000000000000000000000b900000000000000000000000000000000000000000000000000000000000000b980000000000000000000000000000000000000000000000000000000000000ba00000000000000000000000000000000000000000000000000000000000000ba80000000000000000000000000000000000000000000000000000000000000bb00000000000000000000000000000000000000000000000000000000000000bb80000000000000000000000000000000000000000000000000000000000000bc00000000000000000000000000000000000000000000000000000000000000bc80000000000000000000000000000000000000000000000000000000000000bd00000000000000000000000000000000000000000000000000000000000000bd80000000000000000000000000000000000000000000000000000000000000be00000000000000000000000000000000000000000000000000000000000000be80000000000000000000000000000000000000000000000000000000000000bf00000000000000000000000000000000000000000000000000000000000000bf80000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c080000000000000000000000000000000000000000000000000000000000000c100000000000000000000000000000000000000000000000000000000000000c180000000000000000000000000000000000000000000000000000000000000c200000000000000000000000000000000000000000000000000000000000000c280000000000000000000000000000000000000000000000000000000000000c300000000000000000000000000000000000000000000000000000000000000c380000000000000000000000000000000000000000000000000000000000000c400000000000000000000000000000000000000000000000000000000000000c480000000000000000000000000000000000000000000000000000000000000c500000000000000000000000000000000000000000000000000000000000000c580000000000000000000000000000000000000000000000000000000000000c600000000000000000000000000000000000000000000000000000000000000c680000000000000000000000000000000000000000000000000000000000000c700000000000000000000000000000000000000000000000000000000000000c780000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000000c880000000000000000000000000000000000000000000000000000000000000c900000000000000000000000000000000000000000000000000000000000000c980000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000ca80000000000000000000000000000000000000000000000000000000000000cb00000000000000000000000000000000000000000000000000000000000000cb80000000000000000000000000000000000000000000000000000000000000cc00000000000000000000000000000000000000000000000000000000000000cc80000000000000000000000000000000000000000000000000000000000000cd00000000000000000000000000000000000000000000000000000000000000cd80000000000000000000000000000000000000000000000000000000000000ce00000000000000000000000000000000000000000000000000000000000000ce80000000000000000000000000000000000000000000000000000000000000cf00000000000000000000000000000000000000000000000000000000000000cf80000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000d080000000000000000000000000000000000000000000000000000000000000d100000000000000000000000000000000000000000000000000000000000000d180000000000000000000000000000000000000000000000000000000000000d200000000000000000000000000000000000000000000000000000000000000d280000000000000000000000000000000000000000000000000000000000000d300000000000000000000000000000000000000000000000000000000000000d380000000000000000000000000000000000000000000000000000000000000d400000000000000000000000000000000000000000000000000000000000000d480000000000000000000000000000000000000000000000000000000000000d500000000000000000000000000000000000000000000000000000000000000d580000000000000000000000000000000000000000000000000000000000000d600000000000000000000000000000000000000000000000000000000000000d680000000000000000000000000000000000000000000000000000000000000d700000000000000000000000000000000000000000000000000000000000000d780000000000000000000000000000000000000000000000000000000000000d800000000000000000000000000000000000000000000000000000000000000d880000000000000000000000000000000000000000000000000000000000000d900000000000000000000000000000000000000000000000000000000000000d980000000000000000000000000000000000000000000000000000000000000da00000000000000000000000000000000000000000000000000000000000000da80000000000000000000000000000000000000000000000000000000000000db00000000000000000000000000000000000000000000000000000000000000db80000000000000000000000000000000000000000000000000000000000000dc00000000000000000000000000000000000000000000000000000000000000dc80000000000000000000000000000000000000000000000000000000000000dd00000000000000000000000000000000000000000000000000000000000000dd80000000000000000000000000000000000000000000000000000000000000de00000000000000000000000000000000000000000000000000000000000000de80000000000000000000000000000000000000000000000000000000000000df00000000000000000000000000000000000000000000000000000000000000df80000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000e080000000000000000000000000000000000000000000000000000000000000e100000000000000000000000000000000000000000000000000000000000000e180000000000000000000000000000000000000000000000000000000000000e200000000000000000000000000000000000000000000000000000000000000e280000000000000000000000000000000000000000000000000000000000000e300000000000000000000000000000000000000000000000000000000000000e380000000000000000000000000000000000000000000000000000000000000e400000000000000000000000000000000000000000000000000000000000000e480000000000000000000000000000000000000000000000000000000000000e500000000000000000000000000000000000000000000000000000000000000e580000000000000000000000000000000000000000000000000000000000000e600000000000000000000000000000000000000000000000000000000000000e680000000000000000000000000000000000000000000000000000000000000e700000000000000000000000000000000000000000000000000000000000000e780000000000000000000000000000000000000000000000000000000000000e800000000000000000000000000000000000000000000000000000000000000e880000000000000000000000000000000000000000000000000000000000000e900000000000000000000000000000000000000000000000000000000000000e980000000000000000000000000000000000000000000000000000000000000ea00000000000000000000000000000000000000000000000000000000000000ea80000000000000000000000000000000000000000000000000000000000000eb00000000000000000000000000000000000000000000000000000000000000eb80000000000000000000000000000000000000000000000000000000000000ec00000000000000000000000000000000000000000000000000000000000000ec80000000000000000000000000000000000000000000000000000000000000ed00000000000000000000000000000000000000000000000000000000000000ed80000000000000000000000000000000000000000000000000000000000000ee00000000000000000000000000000000000000000000000000000000000000ee80000000000000000000000000000000000000000000000000000000000000ef00000000000000000000000000000000000000000000000000000000000000ef80000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000000f080000000000000000000000000000000000000000000000000000000000000f100000000000000000000000000000000000000000000000000000000000000f180000000000000000000000000000000000000000000000000000000000000f200000000000000000000000000000000000000000000000000000000000000f280000000000000000000000000000000000000000000000000000000000000f300000000000000000000000000000000000000000000000000000000000000f380000000000000000000000000000000000000000000000000000000000000f400000000000000000000000000000000000000000000000000000000000000f480000000000000000000000000000000000000000000000000000000000000f500000000000000000000000000000000000000000000000000000000000000f580000000000000000000000000000000000000000000000000000000000000f600000000000000000000000000000000000000000000000000000000000000f680000000000000000000000000000000000000000000000000000000000000f700000000000000000000000000000000000000000000000000000000000000f780000000000000000000000000000000000000000000000000000000000000f800000000000000000000000000000000000000000000000000000000000000f880000000000000000000000000000000000000000000000000000000000000f900000000000000000000000000000000000000000000000000000000000000f980000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000000000fa80000000000000000000000000000000000000000000000000000000000000fb00000000000000000000000000000000000000000000000000000000000000fb80000000000000000000000000000000000000000000000000000000000000fc00000000000000000000000000000000000000000000000000000000000000fc80000000000000000000000000000000000000000000000000000000000000fd00000000000000000000000000000000000000000000000000000000000000fd80000000000000000000000000000000000000000000000000000000000000fe00000000000000000000000000000000000000000000000000000000000000fe80000000000000000000000000000000000000000000000000000000000000ff00000000000000000000000000000000000000000000000000000000000000ff800000000000000000000000000000000000000000000000000000000 +PayloadSize (Size of the payload in bytes. This is the total number of bytes sent in the payload. Image data + chunk data if present. No packet headers.): 12288000 +GevSCPSPacketSize (Packet size in bytes for the selected stream channel. Excludes data leader and data trailer. (The last packet may be smaller because the packet size is not necessarily a multiple of the block size for the stream channel.)): 1500 +GevSCPD (Delay between the transmission of each packet for the selected stream channel. The delay is measured in ticks.): 0 +GevSCFTD (Frame transmission delay for the selected stream channel (in ticks). This value sets a delay before transmitting the acquired image.): 0 +GevSCBWR (Percentage of the Ethernet bandwidth assigned to the camera to be held in reserve for packet resends and for the transmission of control data between the camera and the host PC. The setting is expressed as a percentage of the bandwidth assigned parameter. For example, if the Bandwidth Assigned parameter indicates that 30 MBytes/s have been assigned to the camera and the Bandwidth Reserve parameter is set to 5%, the bandwidth reserve will be 1.5 MBytes/s.): 10 +GevSCBWRA (Multiplier for the Bandwidth Reserve parameter. The multiplier is used to establish an extra pool of reserved bandwidth that can be used if an unusually large burst of packet resends is needed.): 10 +GevSCBWA (Base bandwidth in bytes per second that will be used by the camera to transmit image and chunk feature data and to handle resends and control data transmissions. This parameter represents a combination of the packet size and the inter-packet delay.): 125000000 +GevSCDMT (Maximum amount of data (in bytes per second) that the camera could generate given its current settings and ideal conditions, i.e., unlimited bandwidth and no packet resends.): 124369426 +GevSCDCT (Actual bandwidth (in bytes per second) that the camera will use to transmit image data and chunk data given the current AOI settings, chunk feature settings, and the pixel format setting.): 112499549 +GevSCFJM (Maximum time (in ticks) that the next frame transmission could be delayed due to a burst of resends. If the Bandwidth Reserve Accumulation parameter is set to a high value, the camera can experience periods where there is a large burst of data resends. This burst of resends will delay the start of transmission of the next acquired image. ): 14382234 +GevVersionMajor (Major version number of the GigE Vision specification supported by this device.): 2 +GevVersionMinor (Minor version number of the GigE Vision specification supported by this device.): 0 +GevDeviceModeIsBigEndian (Indicates whether the bootstrap register is in big-endian format.): True +GevDeviceModeCharacterSet (Character set used by all strings of the bootstrap registers (1 = UTF8).): 1 +GevInterfaceSelector (Sets the physical network interface to be configured. Once a network interface has been selected, all changes to the network interface settings will be applied to the selected interface.): NetworkInterface0 +GevMACAddress (MAC address for the selected network interface.): 207554443575 +GevGVSPExtendedIDMode (Sets the extended ID mode for GVSP (64 bit block_id64, 32 bit packet_id32). This bit cannot be reset if the stream channels do not support the standard ID mode.): Off +GevSupportedIPConfigurationLLA (Indicates whether the selected network interface supports auto IP addressing (also known as LLA).): True +GevSupportedIPConfigurationDHCP (Indicates whether the selected network interface supports DHCP IP addressing.): True +GevSupportedIPConfigurationPersistentIP (Indicates whether the selected network interface supports fixed IP addressing (also known as persistent IP addressing).): True +GevCurrentIPConfiguration (IP configuration of the selected network interface, e.g. fixed IP, DHCP, or auto IP.): 5 +GevCurrentIPAddress (Current IP address for the selected network interface.): 3232236037 +GevCurrentSubnetMask (Current subnet mask for the selected network interface.): 4294967040 +GevCurrentDefaultGateway (Current default gateway for the selected network interface.): 0 +GevPersistentIPAddress (Fixed IP address for the selected network interface (if fixed IP addressing is supported by the device and enabled).): 3232236037 +GevPersistentSubnetMask (Fixed subnet mask for the selected network interface (if fixed IP addressing is supported by the device and enabled).): 4294967040 +GevPersistentDefaultGateway (Fixed default gateway for the selected network interface (if fixed IP addressing is supported by the device and enabled).): 0 +GevLinkSpeed (Connection speed in Mbps for the selected network interface.): 1000 +GevLinkMaster (Indicates whether the selected network interface is the clock master.): True +GevLinkFullDuplex (Indicates whether the selected network interface operates in full-duplex mode.): True +GevLinkCrossover (Indicates the state of medium-dependent interface crossover (MDIX) for the selected network interface.): False +GevFirstURL (First URL reference to the GenICam XML file. ): Local:Basler_Ace_GigE_81a5a494_Version_3_8.zip;c0000000;10ea0 +GevSecondURL (Second URL reference to the GenICam XML file. ): http://www.baslerweb.com/camera/Basler_Ace_GigE_81a5a494_Version_3_8.xml +GevNumberOfInterfaces (Number of network interfaces on the device.): 1 +GevMessageChannelCount (Number of message channels supported by the device.): 1 +GevStreamChannelCount (Number of stream channels supported by the device.): 1 +GevSupportedOptionalLegacy16BitBlockID (Indicates whether this GVSP transmitter or receiver can support 16-bit block_id.): True +GevSupportedIEEE1588 (Indicates whether the IEEE 1588 V2 Precision Time Protocol (PTP) is supported.): True +GevSupportedOptionalCommandsEVENTDATA (Indicates whether EVENTDATA_CMD and EVENTDATA_ACK are supported.): False +GevSupportedOptionalCommandsEVENT (Indicates whether EVENT_CMD and EVENT_ACK are supported.): True +GevSupportedOptionalCommandsPACKETRESEND (Indicates whether PACKETRESEND_CMD is supported.): True +GevSupportedOptionalCommandsWRITEMEM (Indicates whether WRITEMEM_CMD and WRITEMEM_ACK are supported.): True +GevSupportedOptionalCommandsConcatenation (Indicates whether multiple operations in a single message are supported.): True +GevHeartbeatTimeout (Heartbeat timeout in milliseconds.): 3000 +GevTimestampTickFrequency (Number of timestamp clock ticks in 1 second.): 125000000 +GevTimestampControlLatch (Latches the current timestamp value of the device.): +GevTimestampControlReset (Resets the timestamp value for the device.): +GevTimestampControlLatchReset (Resets the timestamp control latch.): +GevTimestampValue (Latched value of the timestamp. (The timestamp must first be latched using the Timestamp Control Latch command.)): 0 +GevCCP (Sets the control channel privilege feature.): Control +GevStreamChannelSelector (Sets the stream channels to be configured. Once a stream channel has been selected, all changes to the stream channel settings will be applied to the selected stream channel.): StreamChannel0 +GevSCPInterfaceIndex (Index of the network interface to use.): 0 +GevSCDA (Stream channel destination IPv4 address for the selected stream channel. The destination can be a unicast or a multicast.): 0 +GevSCPHostPort (Port to which the device must send data streams.): 0 +GevSCPSFireTestPacket (Fires a GigE Vision streaming test packet. When this bit is set and the stream channel is a transmitter, the transmitter will fire one test packet of size specified by GevSCPSPacketSize. The "do not fragment" bit of the IP header must be set for this test packet (see GevSCPSDoNotFragment).): +GevSCPSDoNotFragment (Disables IP fragmentation of packets on the stream channel. This bit is copied into the "do not fragment" bit of the IP header of each stream packet.): True +GevSCPSBigEndian (Returns the endianess of multi-byte pixel data for this stream. True = big endian.): False +GevIEEE1588 (Enables the IEEE 1588 V2 Precision Time Protocol for the timestamp register. Only available when the IEEE1588_support bit of the GVCP Capability register is set. When PTP is enabled, the Timestamp Control register cannot be used to reset the timestamp. Factory default is device specific. When PTP is enabled or disabled, the value of Timestamp Tick Frequency and Timestamp Value registers might change to reflect the new time domain.): False +GevIEEE1588Status (Provides the state of the IEEE 1588 clock. Values of this field must match the IEEE 1588 PTP port state enumeration (INITIALIZING, FAULTY, DISABLED, LISTENING, PRE_MASTER, MASTER, PASSIVE, UNCALIBRATED, SLAVE). Please refer to IEEE 1588 for additional information.): +GevIEEE1588DataSetLatch (Latches the current IEEE 1588 related values of the device.): +GevIEEE1588StatusLatched (Returns the latched state of the IEEE 1588 clock. (The state must first be latched using the IEEE 1588 Latch command.) The state is indicated by values 1 to 9, corresponding to the states INITIALIZING, FAULTY, DISABLED, LISTENING, PRE_MASTER, MASTER, PASSIVE, UNCALIBRATED, and SLAVE. Refer to the IEEE 1588 specification for additional information.): +GevIEEE1588OffsetFromMaster (Latched offset from the IEEE 1588 master clock in nanoseconds. (The offset must first be latched using the IEEE 1588 Latch command.)): +GevIEEE1588ClockIdLow (Low part of the latched clock ID of the IEEE 1588 device.): +GevIEEE1588ClockIdHigh (High part of the latched clock ID of the IEEE 1588 device.): +GevIEEE1588ClockId (Latched clock ID of the IEEE 1588 device. (The clock ID must first be latched using the IEEE 1588 Latch command.) The clock ID is an array of eight octets which is displayed as hexadecimal number. Leading zeros are omitted.): +GevIEEE1588ParentClockIdLow (Low part of the latched parent clock ID of the IEEE 1588 device.): +GevIEEE1588ParentClockIdHigh (High part of the latched parent clock ID of the IEEE 1588 device.): +GevIEEE1588ParentClockId (Latched parent clock ID of the IEEE 1588 device. (The parent clock ID must first be latched using the IEEE 1588 Latch command.) The parent clock ID is the clock ID of the current master clock. A clock ID is an array of eight octets which is displayed as hexadecimal number. Leading zeros are omitted.): +NumberOfActionSignals (Number of separate action signals supported by the device. Determines how many action signals the device can handle in parallel, i.e. how many different action commands can be set up for the device.): 1 +ActionCommandCount (Number of separate action signals supported by the device. Determines how many action signals the device can handle in parallel, i.e. how many different action commands can be set up for the device.): 1 +ActionDeviceKey (Device key used to authorize the execution of an action command. If the action device key in the camera and the action device key in the protocol message are identical, the camera will execute the corresponding action.): 0 +ActionSelector (Sets the action command to be configured. Because you cannot assign more than one action command to a Basler camera at a time, ActionSelector should always be set to 1.): 1 +ActionGroupKey (Group key used to define a group of devices on which action commands can be executed.): 0 +ActionGroupMask (Group mask used to filter out a sub-group of cameras belonging to a group of cameras. The cameras belonging to a sub-group execute an action command at the same time. The filtering is done using a logical bitwise And operation against the group mask number of the action command and the group mask number of a camera. If both binary numbers have at least one common bit set to 1 (i.e. the result of the And operation is non-zero), the corresponding camera belongs to the sub-group.): 0 +DeviceRegistersStreamingStart (Prepare the device for registers streaming.): +DeviceRegistersStreamingEnd (Announce the end of registers streaming.): +UserSetSelector (Sets the user set or the factory set to load, save or configure.): Default +UserSetLoad (Loads the selected set into the camera's volatile memory and makes it the active configuration set. Once the selected set is loaded, the parameters in the selected set will control the camera.): +UserSetSave (Saves the current active set into the selected user set.): +UserSetDefaultSelector (Sets the user set or the factory set to be used as the startup set. The default startup set will be loaded as the active set whenever the camera is powered on or reset.): Default +DefaultSetSelector (Sets the factory set that will be used as the default set.): Standard +AutoTargetValue (Target average brightness for the gain auto function and the exposure auto function.): 128 +GrayValueAdjustmentDampingAbs (Gray value adjustment damping factor. The factor controls the rate by which pixel gray values are changed when the exposure auto function or the gain auto function or both are enabled. This can be useful, for example, when objects move into the camera's view area and the light conditions are gradually changing due to the moving objects.): 0.68359375 +GrayValueAdjustmentDampingRaw (Gray value adjustment damping factor. The factor controls the rate by which pixel gray values are changed when the exposure auto function or the gain auto function or both are enabled. This can be useful, for example, when objects move into the camera's view area and the light conditions are gradually changing due to the moving objects.): 700 +BalanceWhiteAdjustmentDampingAbs (Balance White adjustment damping factor. The factor controls the rate by which colors are adjusted when the balance white auto function is enabled. This can be useful, for example, when objects move into the camera's view area and the light conditions are gradually changing due to the moving objects.): +BalanceWhiteAdjustmentDampingRaw (Balance White adjustment damping factor. The factor controls the rate by which colors are adjusted when the balance white auto function is enabled. This can be useful, for example, when objects move into the camera's view area and the light conditions are gradually changing due to the moving objects.): +AutoGainRawLowerLimit (Lower limit for the Gain parameter when the gain auto function is active.): 0 +AutoGainRawUpperLimit (Upper limit for the Gain parameter when the gain auto function is active.): 240 +AutoExposureTimeAbsLowerLimit (Lower limit for the ExposureTime parameter when the exposure auto function is active.): 80.0 +AutoExposureTimeAbsUpperLimit (Upper limit for the ExposureTime parameter when the exposure auto function is active.): 100000.0 +AutoFunctionProfile (Sets how gain and exposure time will be balanced when the device is making automatic adjustments.): GainMinimum +AutoFunctionAOISelector (Sets which auto function AOI can be adjusted.): AOI1 +AutoFunctionAOIWidth (Width of the auto function AOI (in pixels).): 4096 +AutoFunctionAOIHeight (Height of the auto function AOI (in pixels).): 3000 +AutoFunctionAOIOffsetX (Horizontal offset from the left side of the sensor to the auto function AOI (in pixels).): 8 +AutoFunctionAOIOffsetY (Vertical offset from the top of the sensor to the auto function AOI (in pixels).): 4 +AutoFunctionAOIUsageIntensity (Assigns the gain auto and the exposure auto functions to the currently selected auto function AOI. For this parameter, gain auto and exposure auto are considered as a single intensity" auto function."): True +AutoFunctionAOIUsageWhiteBalance (Assigns the balance white auto function to the currently selected auto function AOI.): False +UserDefinedValueSelector (Sets the user-defined value to set or read.): Value1 +UserDefinedValue (A user defined value. The value can serve as storage location for the camera user. It has no impact on the operation of the camera.): 0 +DeviceVendorName (Name of the device's vendor.): Basler +DeviceModelName (Model name of the device.): acA4112-8gm +DeviceManufacturerInfo (Additional information from the vendor about the camera.): none +DeviceVersion (Version of the device.): 107411-02 +DeviceFirmwareVersion (Version of the device's firmware.): 107411-02;U;acA4112_8g;V1.0-0;0 +DeviceID (ID of the device.): 40006341 +DeviceUserID (User-settable ID of the device.): +DeviceScanType (Returns the scan type of the device's sensor (area or line scan).): Areascan +DeviceReset (Immediately resets and reboots the device.): +TemperatureSelector (Sets the location within the device where the temperature will be measured.): Coreboard +TemperatureAbs (Temperature of the selected location within the device (in degrees centigrade). The temperature is measured at the location set by TemperatureSelector.): 57.0 +TemperatureState (Returns the temperature state.): Ok +CriticalTemperature (Indicates whether the critical temperature has been reached.): False +OverTemperature (An over temperature state has been detected.): False +LastError (Returns the last occurred error.): NoError +ClearLastError (Clears the last error. If a previous error exists, the previous error can be retrieved.): +ParameterSelector (Sets the parameter whose factory limits should be removed. Once a parameter has been set, the factory limits can be removed using RemoveLimits.): Gain +RemoveLimits (Removes the factory-set limits of the selected parameter. Having removed the factory-set limits, you may set the parameter within extended limits. These are only defined by technical restrictions. Note: Inferior image quality may result.): False +ExpertFeatureAccessSelector (Sets the expert feature to be configured. Once a feature has been set, all changes made using the feature enable feature will be applied to this feature.): ExpertFeature1 +ExpertFeatureAccessKey (Key to access the selected expert feature.): 0 +ExpertFeatureEnable (Enables the currently selected expert feature.): +ChunkModeActive (Enables the chunk mode.): False +ChunkSelector (Sets the chunk to be enabled. Once a chunk has been set, the chunk can be enabled using the ChunkEnable parameter.): +ChunkEnable (Enables the inclusion of the currently selected chunk in the payload data.): +ChunkStride (Number of bytes of data between the beginning of one line in the acquired image and the beginning of the next line in the acquired image.): +ChunkSequenceSetIndex (Sequence set index number related to the acquired image.): +ChunkOffsetX (X offset of the area of interest set for the acquired image.): +ChunkOffsetY (Y offset of the area of interest set for the acquired image.): +ChunkWidth (Width of the area of interest set for the acquired image.): +ChunkHeight (Height of the area of interest set for the acquired image.): +ChunkDynamicRangeMin (Minimum possible pixel value in the acquired image.): +ChunkDynamicRangeMax (Maximum possible pixel value in the acquired image.): +ChunkPixelFormat (Returns the pixel format of the acquired image.): +ChunkTimestamp (Value of the timestamp when the image was acquired.): +ChunkFramecounter (Value of the frame counter when the image was acquired.): +ChunkLineStatusAll (Status of all of the camera's input and output lines when the image was acquired.): +ChunkVirtLineStatusAll (Status of all of the camera's virtual input and output lines when the image was acquired.): +ChunkTriggerinputcounter (Value of the trigger input counter when the image was acquired.): +ChunkLineTriggerIgnoredCounter (Value of the line trigger ignored counter when the image was acquired.): +ChunkFrameTriggerIgnoredCounter (Value of the frame trigger ignored counter when the image was acquired.): +ChunkFrameTriggerCounter (Value of the frame trigger counter when the image was acquired.): +ChunkFramesPerTriggerCounter (Value of the frames per trigger counter when the image was acquired.): +ChunkLineTriggerEndToEndCounter (Value of the line trigger end to end counter when the image was acquired.): +ChunkInputStatusAtLineTriggerBitsPerLine (Number of bits per line used for the input line status at line trigger feature.): +ChunkInputStatusAtLineTriggerIndex (Index number used for the InputStatusAtLineTrigger feature. The index number can be used to get the state of the camera's input lines when a specific line was acquired. For example, if you want to know the state of the camera's input lines when line 30 was acquired, set the index to 30, then retrieve the value of ChunkInputStatusAtLineTriggerValue.): +ChunkInputStatusAtLineTriggerValue (Value indicating the status of the camera's input lines when a specific line was acquired. The information is stored in a 4 bit value (bit 0 = input line 1 state, bit 1 = input line 2 state etc.). For more information, see the ChunkInputStatusAtLineTriggerIndex documentation.): +ChunkShaftEncoderCounter (Value of the shaft encoder counter when the image was acquired.): +ChunkExposureTime (Exposure time used to acquire the image.): +ChunkPayloadCRC16 (CRC checksum of the acquired image. The checksum is calculated using all of the image data and all of the appended chunks except for the checksum itself.): +ChunkGainAll (Gain all setting of the acquired image.): +ChunkLineTriggerCounter (Value of the line trigger counter when the image was acquired.): +EventSelector (Sets the event notification to be enabled. Once an event notification has been set, the notification can be enabled using the EventNotification parameter.): ExposureEnd +EventNotification (Enables event notifications for the currently selected event. The event can selected using the EventSelector parameter.): Off +ExposureEndEventStreamChannelIndex (Stream channel index of the exposure end event.): +ExposureEndEventFrameID (Frame ID for an exposure end event.): +ExposureEndEventTimestamp (Time stamp of the exposure end event.): +LineStartOvertriggerEventStreamChannelIndex (Stream channel index of the line start overtrigger event.): +LineStartOvertriggerEventTimestamp (Time stamp of the line start overtrigger event.): +FrameStartOvertriggerEventStreamChannelIndex (Stream channel index of the frame start overtrigger event.): +FrameStartOvertriggerEventTimestamp (Time stamp of the frame start overtrigger event.): +FrameStartEventStreamChannelIndex (Stream channel index of the frame start event.): +FrameStartEventTimestamp (Time stamp of the frame start event.): +AcquisitionStartEventStreamChannelIndex (Stream channel index of the acquisition start event.): +AcquisitionStartEventTimestamp (Time stamp of the acquisition start event.): +AcquisitionStartOvertriggerEventStreamChannelIndex (Stream channel index of the acquisition start overtrigger event.): +AcquisitionStartOvertriggerEventTimestamp (Time stamp of the acquisition start overtrigger event.): +FrameTimeoutEventStreamChannelIndex (Stream channel index of the frame timeout event.): +FrameTimeoutEventTimestamp (Time stamp of the frame timeout event.): +EventOverrunEventStreamChannelIndex (Stream channel index of the event overrun event.): +EventOverrunEventFrameID (Frame ID for an event overrun event.): +EventOverrunEventTimestamp (Time stamp of the event overrun event.): +CriticalTemperatureEventStreamChannelIndex (Stream channel index of the critical temperature event.): +CriticalTemperatureEventTimestamp (Time stamp of the critical temperature event.): +OverTemperatureEventStreamChannelIndex (Stream channel index of the over temperature event.): +OverTemperatureEventTimestamp (Time stamp of the over temperature event.): +ActionLateEventStreamChannelIndex (Stream channel index of the action late event. A action late event is raised when a scheduled action command with a timestamp in the past is received.): +ActionLateEventTimestamp (Time stamp of the action late event. A action late event is raised when a scheduled action command with a timestamp in the past is received.): +LateActionEventStreamChannelIndex (): +LateActionEventTimestamp (): +Line1RisingEdgeEventStreamChannelIndex (Stream channel index of the I/O line 1 rising edge event.): +Line1RisingEdgeEventTimestamp (Time stamp of the line 1 rising edge event.): +Line2RisingEdgeEventStreamChannelIndex (Stream channel index of the I/O line 2 rising edge event.): +Line2RisingEdgeEventTimestamp (Time stamp of the line 2 rising edge event.): +Line3RisingEdgeEventStreamChannelIndex (Stream channel index of the I/O line 3 rising edge event.): +Line3RisingEdgeEventTimestamp (Time stamp of the line 3 rising edge event.): +Line4RisingEdgeEventStreamChannelIndex (Stream channel index of the I/O line 4 rising edge event.): +Line4RisingEdgeEventTimestamp (Time stamp of the line 4 rising edge event.): +VirtualLine1RisingEdgeEventStreamChannelIndex (Stream channel index of the virtual line 1 rising edge event.): +VirtualLine1RisingEdgeEventTimestamp (Time stamp of the virtual line 1 rising edge event.): +VirtualLine2RisingEdgeEventStreamChannelIndex (Stream channel index of the virtual line 2 rising edge event.): +VirtualLine2RisingEdgeEventTimestamp (Time stamp of the virtual line 2 rising edge event.): +VirtualLine3RisingEdgeEventStreamChannelIndex (Stream channel index of the virtual line 3 rising edge event.): +VirtualLine3RisingEdgeEventTimestamp (Time stamp of the virtual line 3 rising edge event.): +VirtualLine4RisingEdgeEventStreamChannelIndex (Stream channel index of the virtual line 4 rising edge event.): +VirtualLine4RisingEdgeEventTimestamp (Time stamp of the virtual line 4 rising edge event.): +FrameStartWaitEventStreamChannelIndex (Stream channel index of the frame start wait event.): +FrameStartWaitEventTimestamp (Time stamp of the frame start wait event.): +AcquisitionStartWaitEventStreamChannelIndex (Stream channel index of the acquisition start wait event.): +AcquisitionStartWaitEventTimestamp (Time stamp of the acquisition start wait event.): +FileSelector (Sets the target file in the device.): UserSet1 +FileOperationSelector (Sets the target operation for the currently selected file. After an operation has been selected, the operation can be executed using the FileOperationExecute command.): Open +FileOpenMode (Sets the access mode in which a file is opened in the device.): Read +FileAccessBuffer (Access buffer for file operations.): 0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +FileAccessOffset (File access offset. Controls the mapping between the device file storage and the FileAccessBuffer.): 0 +FileAccessLength (File access length. Controls the mapping between the device file storage and the FileAccessBuffer.): 0 +FileOperationStatus (Returns the file operation execution status.): Success +FileOperationResult (File operation result. For read or write operations, the number of successfully read/written bytes is returned.): 0 +FileSize (Size of the currently selected file in bytes.): 16 +FileOperationExecute (Executes the operation selected by FileOperationSelector on the selected file.): diff --git a/lantz/drivers/lantz_server.py b/lantz/drivers/lantz_server.py index 912dc8d..a13833d 100644 --- a/lantz/drivers/lantz_server.py +++ b/lantz/drivers/lantz_server.py @@ -121,34 +121,61 @@ def handle(self): super().__init__((host, port), Lantz_Handler) -class Lantz_Base_Client(Driver): - def __init__(self, host, port, timeout=1): - self.host = host - self.port = port - self.timeout = timeout +# class Lantz_Base_Client(Driver): +# def __init__(self, host, port, timeout=1): +# self.host = host +# self.port = port +# self.timeout = timeout - def query(self, data): - #Initialize and send query - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect((self.host, self.port)) - sock.sendall(encode_data(data)) +# def query(self, data): +# #Initialize and send query +# sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +# sock.connect((self.host, self.port)) +# sock.sendall(encode_data(data)) - #Read back ans from the server - return receive_all(sock.recv, self.timeout) +# #Read back ans from the server +# ans = receive_all(sock.recv, self.timeout) + +# sock.close() +# return ans class Device_Client(): - def __new__(cls, device_driver_class, host, port, timeout=1): - + def __new__(cls, device_driver_class, host, port, timeout=1, allow_initialize_finalize=True): if type(device_driver_class) is str: class_name = device_driver_class.split('.')[-1] mod = import_module(device_driver_class.replace('.'+class_name, '')) device_driver_class = getattr(mod, class_name) - class Device_Client_Instance(Lantz_Base_Client): + class Device_Client_Instance(device_driver_class): __name__ = '_Device_Client.' + device_driver_class.__name__ - __qualname__ = 'Device_Client.' + device_driver_class.__name__ + __qualname__ = 'Device_Client.' + device_driver_class.__name__ + _allow_initialize_finalize = allow_initialize_finalize + def initialize(self): + if self._allow_initialize_finalize: + self._initialize() + def finalize(self): + if self._allow_initialize_finalize: + self._finalize() + + def __init__(self, host, port, timeout=1): + self.host = host + self.port = port + self.timeout = timeout + + + def query(self, data): + #Initialize and send query + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((self.host, self.port)) + sock.sendall(encode_data(data)) + + #Read back ans from the server + ans = receive_all(sock.recv, self.timeout) + + sock.close() + return ans for feat_name, feat in device_driver_class._lantz_features.items(): if isinstance(feat, Feat): @@ -176,16 +203,18 @@ def f_(_self, val): for action_name, action in device_driver_class._lantz_actions.items(): def execute(_action_name): def f_(_self, *args, **kwargs): - print(_action_name) - print(args) - print(kwargs) reply = _self.query(build_query('Action', _action_name, args=args, kwargs=kwargs)) if not reply['error'] is None: raise reply['error'] else: return reply['msg'] return f_ - setattr(Device_Client_Instance, action_name, execute(action_name)) + if action_name in ['initialize', 'finalize']: + setattr(Device_Client_Instance, '_'+action_name, execute(action_name)) + else: + setattr(Device_Client_Instance, action_name, execute(action_name)) + + obj = Device_Client_Instance.__new__(Device_Client_Instance) obj.__init__(host, port, timeout=timeout) diff --git a/lantz/drivers/motion/__init__.py b/lantz/drivers/motion/__init__.py new file mode 100644 index 0000000..fa83208 --- /dev/null +++ b/lantz/drivers/motion/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +""" + lantz.drivers.motion + ~~~~~~~~~~~~~~~~~~~~~ + + :company: Motion Controller + :description: General class for equipment that can translate or rotate + :website: + + --- + + :copyright: 2015, see AUTHORS for more details. + :license: GPLv3, + +""" + +from .motioncontroller import MotionControllerMultiAxis, MotionControllerSingleAxis +from .axis import MotionAxisSingle, MotionAxisMultiple, BacklashMixing + +__all__ = ['MotionControllerMultiAxis', 'MotionControllerSingleAxis', 'MotionAxisSingle', 'MotionAxisMultiple', 'BacklashMixing'] diff --git a/lantz/drivers/motion/axis.py b/lantz/drivers/motion/axis.py new file mode 100644 index 0000000..757eec5 --- /dev/null +++ b/lantz/drivers/motion/axis.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +""" + lantz.drivers.newport.motion axis + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + General class that implements the commands used for motion + drivers + + :copyright: 2018, see AUTHORS for more details. + :license: GPL, see LICENSE for more details. + + +""" + + +from lantz.feat import Feat +from lantz.action import Action +from lantz.driver import Driver +from lantz import Q_ +from lantz.processors import convert_to +import time +import numpy as np + +# Add generic units: +# ureg.define('unit = unit') +# ureg.define('encodercount = encodercount') +# ureg.define('motorstep = motorstep') + + +class MotionAxisSingle(Driver): + def __init__(self, *args, **kwargs): + self.wait_time = 0.01 # in seconds * Q_(1, 's') + self.wait_until_done = True + self.accuracy = 0.001 # in units reported by axis + self._units = 'mm' + + @Feat() + def idn(self): + return self.query('ID?') + + @Feat() + def position(self): + raise NotImplementedError + + @position.setter + def position(self, pos): + """ + Waits until movement is done if self.wait_until_done = True. + + :param pos: new position + """ + self._set_position(pos, wait=self.wait_until_done) + + @Action(units=['mm', None]) + def _set_position(self, pos, wait=None): + """ + Move to an absolute position, taking into account backlash. + + When self.backlash is to a negative value the stage will always move + from low to high values. If necessary, a extra step with length + self.backlash is set. + + :param pos: New position in mm + :param wait: wait until stage is finished + """ + + # First do move to extra position if necessary + + self.__set_position(pos) + if wait: + self._wait_until_done() + self.check_position(pos) + + def __set_position(self, pos): + """ + Move stage to a certain position + :param pos: New position + """ + self.write('PA%f' % (pos)) + + @Action(units='mm') + def check_position(self, pos): + '''Check is stage is at expected position''' + if np.isclose(self.position, pos, atol=self.accuracy): + return True + self.log_error('Position accuracy {} is not reached.' + 'Expected: {}, measured: {}'.format(self.accuracy, + pos, + self.position)) + return False + + + @Feat(values={True: '1', False: '0'}) + def motion_done(self): + raise NotImplementedError + + @Feat() + def units(self): + return self._units + + @units.setter + def units(self, units): + super().update_units(self._units, units) + self._units = units + + def _wait_until_done(self): + wait_time = convert_to('seconds', on_dimensionless='ignore')(self.wait_time) + time.sleep(wait_time) + while not self.motion_done: + time.sleep(wait_time) + return True + + +class MotionAxisMultiple(MotionAxisSingle): + def __init__(self, parent, num, id, *args, **kwargs): + super().__init__(*args, **kwargs) + self.parent = parent + self.num = num + self._idn = id + + def __del__(self): + self.parent = None + self.num = None + + def query(self, command, *, send_args=(None, None), recv_args=(None, None)): + return self.parent.query('{:d}{}'.format(self.num, command), + send_args=send_args, recv_args=recv_args) + + def write(self, command, *args, **kwargs): + return self.parent.write('{:d}{}'.format(self.num, command), + *args, **kwargs) + +class BacklashMixing(): + '''Adds functionality to a motionaxis: blacklash correction''' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.backlash = 0 + + @Action(units=['mm', None]) + def _set_position(self, pos, wait=None): + """ + Move to an absolute position, taking into account backlash. + + When self.backlash is to a negative value the stage will always move + from low to high values. If necessary, a extra step with length + self.backlash is set. + + :param pos: New position in mm + :param wait: wait until stage is finished + """ + + # First do move to extra position if necessary + if self.backlash: + position = self.position.magnitude + backlash = convert_to(self.units, on_dimensionless='ignore' + )(self.backlash).magnitude + if (backlash < 0 and position > pos) or\ + (backlash > 0 and position < pos): + + self.log_info('Using backlash') + self.__set_position(pos + backlash) + self._wait_until_done() + + # Than move to final position + self.__set_position(pos) + if wait: + self._wait_until_done() + self.check_position(pos) + + def __set_position(self, pos): + """ + Move stage to a certain position + :param pos: New position + """ + self.write('PA%f' % (pos)) + self.last_set_position = pos \ No newline at end of file diff --git a/lantz/drivers/motion/motioncontroller.py b/lantz/drivers/motion/motioncontroller.py new file mode 100644 index 0000000..d572a72 --- /dev/null +++ b/lantz/drivers/motion/motioncontroller.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +""" + lantz.drivers.newport.motion axis + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + General class that implements the commands used for motion + drivers + + :copyright: 2018, see AUTHORS for more details. + :license: GPL, see LICENSE for more details. + + +""" + +import time +import numpy as np + +from lantz.feat import Feat +from lantz.action import Action +from lantz.driver import Driver +from pyvisa import constants +from lantz import Q_, ureg +from lantz.processors import convert_to + +from .axis import MotionAxisSingle, MotionAxisMultiple + +# Add generic units: +# ureg.define('unit = unit') +# ureg.define('encodercount = count') +# ureg.define('motorstep = step') + + +class MotionControllerMultiAxis(Driver): + """ Motion controller that can detect multiple axis + + """ + def initialize(self): + super().initialize() + + @Feat() + def idn(self): + raise AttributeError('Not implemented') + + @Action() + def detect_axis(self): + """ Find the number of axis available. + + The detection stops as soon as an empty controller is found. + """ + pass + + @Action() + def get_errors(self): + raise AttributeError('Not implemented') + + @Feat(read_once=False) + def position(self): + return [axis.position for axis in self.axes] + + @Feat(read_once=False) + def _position_cached(self): + return [axis.recall('position') for axis in self.axes] + + @position.setter + def position(self, pos): + """Move to position (x,y,...)""" + return self._position(pos) + + @Action() + def _position(self, pos, read_pos=None, wait_until_done=True): + """Move to position (x,y,...)""" + if read_pos is not None: + self.log_error('kwargs read_pos for function _position is deprecated') + + for p, axis in zip(pos, self.axes): + if p is not None: + axis._set_position(p, wait=False) + if wait_until_done: + for p, axis in zip(pos, self.axes): + if p is not None: + axis._wait_until_done() + axis.check_position(p) + return self.position + + return pos + + @Action() + def motion_done(self): + for axis in self.axes: + axis._wait_until_done() + + def finalize(self): + for axis in self.axes: + if axis is not None: + del (axis) + super().finalize() + + + +class MotionControllerSingleAxis(MotionAxisSingle): + """ Motion controller that can only has sinlge axis + + """ + def initialize(self): + super().initialize() \ No newline at end of file diff --git a/lantz/drivers/newport_motion/.gitignore b/lantz/drivers/newport_motion/.gitignore new file mode 100644 index 0000000..abf4293 --- /dev/null +++ b/lantz/drivers/newport_motion/.gitignore @@ -0,0 +1,61 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Vim backups and swapfiles +*~ +.*.sw? diff --git a/lantz/drivers/newport_motion/AUTHORS b/lantz/drivers/newport_motion/AUTHORS new file mode 100644 index 0000000..44c8c01 --- /dev/null +++ b/lantz/drivers/newport_motion/AUTHORS @@ -0,0 +1 @@ +Vasco Tenner diff --git a/lantz/drivers/newport_motion/LICENSE b/lantz/drivers/newport_motion/LICENSE new file mode 100644 index 0000000..733c072 --- /dev/null +++ b/lantz/drivers/newport_motion/LICENSE @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + 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 3 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, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/lantz/drivers/newport_motion/__init__.py b/lantz/drivers/newport_motion/__init__.py new file mode 100644 index 0000000..45abba4 --- /dev/null +++ b/lantz/drivers/newport_motion/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +""" + lantz.drivers.newport + ~~~~~~~~~~~~~~~~~~~~~ + + :company: Newport. + :description: Test and Measurement Equipment. + :website: http://www.newport.com/ + + --- + + :copyright: 2015, see AUTHORS for more details. + :license: GPLv3, + +""" + +from .motionesp301 import ESP301, ESP301Axis +from .motionsmc100 import SMC100 + +__all__ = ['ESP301', 'ESP301Axis', 'SMC100'] diff --git a/lantz/drivers/newport_motion/motion.py b/lantz/drivers/newport_motion/motion.py new file mode 100644 index 0000000..8bad148 --- /dev/null +++ b/lantz/drivers/newport_motion/motion.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +""" + lantz.drivers.newport.motion axis + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + General class that implements the commands used for several newport motion + drivers + + :copyright: 2018, see AUTHORS for more details. + :license: GPL, see LICENSE for more details. + + Source: Instruction Manual (Newport) + +""" + + +from lantz.feat import Feat +from lantz.action import Action +from lantz.messagebased import MessageBasedDriver +from pyvisa import constants +from lantz import Q_, ureg +from lantz.processors import convert_to +from lantz.drivers.motion import MotionAxisMultiple, MotionControllerMultiAxis, BacklashMixing +import time +import numpy as np + +# Add generic units: +# ureg.define('unit = unit') +# ureg.define('encodercount = count') +# ureg.define('motorstep = step') + + +class MotionController(): + """ Newport motion controller. It assumes all axes to have units mm + + """ + print("newton_motion.MotionController is deprecated. Use lantz.drivers.motion.MotionControllerMultiAxis instead") + + +UNITS = {0: 'encoder count', + 1: 'motor step', + 2: 'millimeter', + 3: 'micrometer', + 4: 'inches', + 5: 'milli-inches', + 6: 'micro-inches', + 7: 'degree', + 8: 'gradian', + 9: 'radian', + 10: 'milliradian', + 11: 'microradian', } + + +class MotionAxis(MotionAxisMultiple, BacklashMixing): + def __del__(self): + self.parent = None + self.num = None + + def query(self, command, *, send_args=(None, None), recv_args=(None, None)): + return self.parent.query('{:d}{}'.format(self.num, command), + send_args=send_args, recv_args=recv_args) + + def write(self, command, *args, **kwargs): + return self.parent.write('{:d}{}'.format(self.num, command), + *args, **kwargs) + + @Feat() + def idn(self): + return self.query('ID?') + + @Action() + def on(self): + """Put axis on""" + self.write('MO') + + @Action() + def off(self): + """Put axis on""" + self.write('MF') + + @Feat(values={True: '1', False: '0'}) + def is_on(self): + """ + :return: True is axis on, else false + """ + return self.query('MO?') + + @Action(units='mm') + def define_home(self, val=0): + """Remap current position to home (0), or to new position + + :param val: new position""" + self.write('DH%f' % val) + + @Action() + def home(self): + """Execute the HOME command""" + self.write('OR') + + @Feat(units='mm') + def position(self): + return self.query('TP?') + + @position.setter + def position(self, pos): + """ + Waits until movement is done if self.wait_until_done = True. + + :param pos: new position + """ + if not self.is_on: + self.log_error('Axis not enabled. Not moving!') + return + + # First do move to extra position if necessary + self._set_position(pos, wait=self.wait_until_done) + + + def __set_position(self, pos): + """ + Move stage to a certain position + :param pos: New position + """ + self.write('PA%f' % (pos)) + self.last_set_position = pos + + @Feat(units='mm/s') + def max_velocity(self): + return float(self.query('VU?')) + + @max_velocity.setter + def max_velocity(self, velocity): + self.write('VU%f' % (velocity)) + + @Feat(units='mm/s**2') + def max_acceleration(self): + return float(self.query('AU?')) + + @max_acceleration.setter + def max_acceleration(self, velocity): + self.write('AU%f' % (velocity)) + + @Feat(units='mm/s') + def velocity(self): + return float(self.query('VA?')) + + @velocity.setter + def velocity(self, velocity): + """ + :param velocity: Set the velocity that the axis should use when moving + :return: + """ + self.write('VA%f' % (velocity)) + + @Feat(units='mm/s**2') + def acceleration(self): + return float(self.query('VA?')) + + @acceleration.setter + def acceleration(self, acceleration): + """ + :param acceleration: Set the acceleration that the axis should use + when starting + :return: + """ + self.write('AC%f' % (acceleration)) + + @Feat(units='mm/s') + def actual_velocity(self): + return float(self.query('TV')) + + @actual_velocity.setter + def actual_velocity(self, val): + raise NotImplementedError + + @Action() + def stop(self): + """Emergency stop""" + self.write('ST') + + @Feat(values={True: '1', False: '0'}) + def motion_done(self): + return self.query('MD?') + + # Not working yet, see https://github.com/hgrecco/lantz/issues/35 + # @Feat(values={Q_('encodercount'): 0, + # Q_('motor step'): 1, + # Q_('millimeter'): 2, + # Q_('micrometer'): 3, + # Q_('inches'): 4, + # Q_('milli-inches'): 5, + # Q_('micro-inches'): 6, + # Q_('degree'): 7, + # Q_('gradian'): 8, + # Q_('radian'): 9, + # Q_('milliradian'): 10, + # Q_('microradian'): 11}) + @Feat() + def units(self): + ret = int(self.query(u'SN?')) + return UNITS[ret] + + @units.setter + def units(self, val): + # No check implemented yet + self.write('%SN%' % (self.num, UNITS.index(val))) + super().units = val + + def _wait_until_done(self): + # wait_time = convert_to('seconds', on_dimensionless='warn')(self.wait_time) + time.sleep(self.wait_time) + while not self.motion_done: + time.sleep(self.wait_time) + return True diff --git a/lantz/drivers/newport_motion/motionesp301.py b/lantz/drivers/newport_motion/motionesp301.py new file mode 100644 index 0000000..7a1f96c --- /dev/null +++ b/lantz/drivers/newport_motion/motionesp301.py @@ -0,0 +1,390 @@ +# -*- coding: utf-8 -*- +""" + lantz.drivers.newport.motionesp301 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Implements the drivers to control ESP300 and ESP301 motion controller + via USB or serial. + + For USB, one first have to install the windows driver from newport. + + :copyright: 2015, see AUTHORS for more details. + :license: GPL, see LICENSE for more details. + + Source: Instruction Manual (Newport) +""" + + +from lantz.feat import Feat +from lantz.action import Action +#from lantz.serial import SerialDriver +from lantz.messagebased import MessageBasedDriver +from pyvisa import constants +#from lantz.visa import GPIBVisaDriver +from lantz import Q_, ureg +from lantz.processors import convert_to +import time +import numpy as np +import copy + +# Add generic units: +#ureg.define('unit = unit') +#ureg.define('encodercount = count') +#ureg.define('motorstep = step') + + +class ESP301(MessageBasedDriver): + """ Newport ESP301 motion controller. It assumes all axes to have units mm + + :param scan_axes: Should one detect and add axes to the controller + """ + + DEFAULTS = { + 'COMMON': {'write_termination': '\r\n', + 'read_termination': '\r\n',}, + 'ASRL':{ + 'timeout': 4000, #ms + 'encoding': 'ascii', + 'data_bits': 8, + 'baud_rate': 19200, + 'parity': constants.Parity.none, + 'stop_bits': constants.StopBits.one, + 'flow_control': constants.VI_ASRL_FLOW_RTS_CTS,#constants.VI_ASRL_FLOW_NONE, + }, + } + + def initialize(self): + super().initialize() + self.detect_axis() + + @classmethod + def via_usb(cls, port, name=None, **kwargs): + """Connect to the ESP301 via USB. Internally this goes via serial""" + cls.DEFAULTS = copy.deepcopy(cls.DEFAULTS) + cls.DEFAULTS['ASRL'].update({'baud_rate': 921600}) + return cls.via_serial(port=port, name=name, **kwargs) + + + @Action() + def detect_axis(self): + """ Find the number of axis available. + + The detection stops as soon as an empty controller is found. + """ + self.axes = [] + i = 0 + scan_axes = True + while scan_axes: + try: + i += 1 + id = self.query('%dID?' % i) + axis = ESP301Axis(self, i, id) + self.axes.append(axis) + except: + err = self.get_errors() + if err == 37: # Axis number missing + self.axes.append(None) + elif err == 9: # Axis number out of range + scan_axes = False + elif err == 6: # Axis number out of range, but wrong errorcode + scan_axes = False + else: # Dunno... + raise Exception(err) + @Action() + def get_errors(self): + err = int(self.query('TE?')) + return err + + @Feat(read_once=False) + def position(self): + return [axis.position for axis in self.axes] + + @Feat(read_once=False) + def _position_cached(self): + return [axis._position_cached for axis in self.axes] + + @position.setter + def position(self, pos): + """Move to position (x,y,...)""" + return self._position(pos) + + @Action() + def _position(self, pos, read_pos=None, wait_until_done=True): + """Move to position (x,y,...)""" + if read_pos is not None: + self.log_error('kwargs read_pos for function _position is deprecated') + + for p, axis in zip(pos, self.axes): + if not p is None: + axis._set_position(p, wait=False) + if wait_until_done: + for p, axis in zip(pos, self.axes): + if not p is None: + axis._wait_until_done() + axis.check_position(p) + return self.position + + return pos + + @Action() + def motion_done(self): + for axis in self.axes: axis._wait_until_done() + + def finalize(self): + for axis in self.axes: + if axis is not None: + del (axis) + super().finalize() + + + + +#class ESP301GPIB( ESP301, GPIBVisaDriver): +# """ Untested! +# """ +# def __init__(self, scan_axes=True, resource_name= 'GPIB0::2::INSTR', *args, **kwargs): +# # Read number of axes and add axis objects +# self.scan_axes = scan_axes +# super().__init__(resource_name=resource_name, *args, **kwargs) + + +class ESP301Axis(ESP301): + def __init__(self, parent, num, id, *args, **kwargs): + #super(ESP301Axis, self).__init__(*args, **kwargs) + self.parent = parent + self.num = num + self.id = id + self.wait_time = 0.01 # in seconds * Q_(1, 's') + self.backlash = 0 + self.wait_until_done = True + self.accuracy = 0.001 # in units reported by axis + # Fill position cache: + self.position + + def __del__(self): + self.parent = None + self.num = None + + def id(self): + return self.id + + @Action() + def on(self): + """Put axis on""" + self.parent.write('%dMO' % self.num) + + @Action() + def off(self): + """Put axis on""" + self.parent.write('%dMF' % self.num) + + @Feat(values={True: '1', False: '0'}) + def is_on(self): + """ + :return: True is axis on, else false + """ + return self.parent.query('%dMO?' % self.num) + + @Action(units='mm') + def define_home(self, val=0): + """Remap current position to home (0), or to new position + + :param val: new position""" + self.parent.write('%dDH%f' % (self.num, val)) + + @Feat(units='mm', read_once=False) + def _position_cached(self): + return self.__position_cached + + @_position_cached.setter + def _position_cached(self, pos): + self.__position_cached = pos + + @Feat(units='mm') + def position(self): + self._position_cached = float(self.parent.query('%dTP?' % self.num))*ureg.mm + return self._position_cached + + @position.setter + def position(self, pos): + """ + Waits until movement is done if self.wait_until_done = True. + + :param pos: new position + """ + if not self.is_on: + self.log_error('Axis not enabled. Not moving!') + return + + # First do move to extra position if necessary + self._set_position(pos, wait=self.wait_until_done) + + @Action(units=['mm',None]) + def _set_position(self, pos, wait=None): + """ + Move to an absolute position, taking into account backlash. + + When self.backlash is to a negative value the stage will always move + from low to high values. If necessary, a extra step with length + self.backlash is set. + + :param pos: New position in mm + :param wait: wait until stage is finished + """ + + # First do move to extra position if necessary + if self.backlash: + position = self.position.magnitude + #backlash = self.backlash.to('mm').magnitude + backlash = convert_to('mm', on_dimensionless='ignore')(self.backlash).magnitude + if ( backlash < 0 and position > pos) or\ + ( backlash > 0 and position < pos): + + self.log_info('Using backlash') + self.__set_position(pos + backlash) + self._wait_until_done() + + # Than move to final position + self.__set_position(pos) + if wait: + self._wait_until_done() + self.check_position(pos) + + def __set_position(self, pos): + """ + Move stage to a certain position + :param pos: New position + """ + self.parent.write('%dPA%f' % (self.num, pos)) + + def check_position(self, pos): + '''Check is stage is at expected position''' + if np.isclose(self.position, pos, atol=self.accuracy): + return True + self.log_error('Position accuracy {} is not reached.' + 'Expected: {}, measured: {}'.format(self.accuracy, pos, self._position_cached)) + return False + + @Feat(units='mm/s') + def max_velocity(self): + return float(self.parent.query('%dVU?' % self.num)) + + @max_velocity.setter + def max_velocity(self, velocity): + self.parent.write('%dVU%f' % (self.num, velocity)) + + @Feat(units='mm/s**2') + def max_acceleration(self): + return float(self.parent.query('%dAU?' % self.num)) + + @max_acceleration.setter + def max_acceleration(self, velocity): + self.parent.write('%dAU%f' % (self.num, velocity)) + + + @Feat(units='mm/s') + def velocity(self): + return float(self.parent.query('%dVA?' % self.num)) + + @velocity.setter + def velocity(self, velocity): + """ + :param velocity: Set the velocity that the axis should use when moving + :return: + """ + self.parent.write('%dVA%f' % (self.num, velocity)) + + @Feat(units='mm/s**2') + def acceleration(self): + return float(self.parent.query('%dVA?' % self.num)) + + @acceleration.setter + def acceleration(self, acceleration): + """ + :param acceleration: Set the acceleration that the axis should use when starting + :return: + """ + self.parent.write('%dAC%f' % (self.num, acceleration)) + + @Feat(units='mm/s') + def actual_velocity(self): + return float(self.parent.query('%dTV' % self.num)) + + @actual_velocity.setter + def actual_velocity(self, val): + raise NotImplementedError + + @Action() + def stop(self): + """Emergency stop""" + self.parent.write(u'{0:d}ST'.format(self.num)) + + @Feat(values={True: '1', False: '0'}) + def motion_done(self): + while True: + ret = self.parent.query('%dMD?' % self.num) + if ret in ['1','0']: + break + else: + time.sleep(self.wait_time) + return ret + + # Not working yet, see https://github.com/hgrecco/lantz/issues/35 + # @Feat(values={Q_('encodercount'): 0, + # Q_('motor step'): 1, + # Q_('millimeter'): 2, + # Q_('micrometer'): 3, + # Q_('inches'): 4, + # Q_('milli-inches'): 5, + # Q_('micro-inches'): 6, + # Q_('degree'): 7, + # Q_('gradian'): 8, + # Q_('radian'): 9, + # Q_('milliradian'): 10, + # Q_('microradian'): 11}) + def units(self): + ret = int(self.parent.query(u'{}SN?'.format(self.num))) + vals = {0 :'encoder count', + 1 :'motor step', + 2 :'millimeter', + 3 :'micrometer', + 4 :'inches', + 5 :'milli-inches', + 6 :'micro-inches', + 7 :'degree', + 8 :'gradian', + 9 :'radian', + 10:'milliradian', + 11:'microradian',} + return vals[ret] + + # @units.setter + # def units(self, val): + # self.parent.write('%SN%' % (self.num, val)) + + def _wait_until_done(self): + #wait_time = convert_to('seconds', on_dimensionless='warn')(self.wait_time) + time.sleep(self.wait_time) + while not self.motion_done: + time.sleep(self.wait_time) #wait_time.magnitude) + + + +if __name__ == '__main__': + import argparse + import lantz.log + + parser = argparse.ArgumentParser(description='Test ESP301 driver') + parser.add_argument('-p', '--port', type=str, default='1', + help='Serial port to connect to') + + args = parser.parse_args() + lantz.log.log_to_socket(lantz.log.DEBUG) + + with ESP301.via_usb(port=args.port) as inst: + # inst.initialize() # Initialize the communication with the power meter + # Find the status of all axes: + for axis in inst.axes: + print('Axis {} Position {} is_on {} max_velocity {} velocity {}'.format(axis.num, axis.position, + axis.is_on, axis.max_velocity, + axis.velocity)) diff --git a/lantz/drivers/newport_motion/motionsmc100.py b/lantz/drivers/newport_motion/motionsmc100.py new file mode 100644 index 0000000..f2f872c --- /dev/null +++ b/lantz/drivers/newport_motion/motionsmc100.py @@ -0,0 +1,348 @@ +# -*- coding: utf-8 -*- +""" + lantz.drivers.newport.motionsmc100 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Implements the drivers to control SMC100 controller + + :copyright: 2018, see AUTHORS for more details. + :license: GPL, see LICENSE for more details. + + Source: Instruction Manual (Newport) + +""" + + +from lantz.feat import Feat +from lantz.action import Action +import pyvisa +from pyvisa import constants + +from lantz import Q_, ureg +from lantz.processors import convert_to +from lantz.messagebased import MessageBasedDriver + +from lantz.drivers.newport_motion.motion import MotionAxis +from lantz.drivers.motion import MotionControllerMultiAxis + +import time +import numpy as np + +ERRORS = {"@": "", + "A": "Unknown message code or floating point controller address.", + "B": "Controller address not correct.", + "C": "Parameter missing or out of range.", + "D": "Execution not allowed.", + "E": "home sequence already started.", + "I": "Execution not allowed in CONFIGURATION state.", + "J": "Execution not allowed in DISABLE state.", + "H": "Execution not allowed in NOT REFERENCED state.", + "K": "Execution not allowed in READY state.", + "L": "Execution not allowed in HOMING state.", + "M": "Execution not allowed in MOVING state.", + } + +positioner_errors = { + 0b1000000000: '80 W output power exceeded', + 0b0100000000: 'DC voltage too low', + 0b0010000000: 'Wrong ESP stage', + 0b0001000000: 'Homing time out', + 0b0000100000: 'Following error', + 0b0000010000: 'Short circuit detection', + 0b0000001000: 'RMS current limit', + 0b0000000100: 'Peak current limit', + 0b0000000010: 'Positive end of run', + 0b0000000001: 'Negative end of run', + } +controller_states = { + '0A': 'NOT REFERENCED from reset.', + '0B': 'NOT REFERENCED from HOMING.', + '0C': 'NOT REFERENCED from CONFIGURATION.', + '0D': 'NOT REFERENCED from DISABLE.', + '0E': 'NOT REFERENCED from READY.', + '0F': 'NOT REFERENCED from MOVING.', + '10': 'NOT REFERENCED ESP stage error.', + '11': 'NOT REFERENCED from JOGGING.', + '14': 'CONFIGURATION.', + '1E': 'HOMING commanded from RS-232-C.', + '1F': 'HOMING commanded by SMC-RC.', + '28': 'MOVING.', + '32': 'READY from HOMING.', + '33': 'READY from MOVING.', + '34': 'READY from DISABLE.', + '35': 'READY from JOGGING.', + '3C': 'DISABLE from READY.', + '3D': 'DISABLE from MOVING.', + '3E': 'DISABLE from JOGGING.', + '46': 'JOGGING from READY.', + '47': 'JOGGING from DISABLE.', + } + + +class SMC100(MessageBasedDriver, MotionControllerMultiAxis): + """ Newport SMC100 motion controller. It assumes all axes to have units mm + + + Example: + import numpy as np + import lantz + import visa + import lantz.drivers.pi.piezo as pi + from lantz.drivers.newport_motion import SMC100 + from pyvisa import constants + rm = visa.ResourceManager('@py') + lantz.messagebased._resource_manager = rm + ureg = lantz.ureg + try: + lantzlog + except NameError: + lantzlog = lantz.log.log_to_screen(level=lantz.log.DEBUG) + lantz.log.log_to_socket(level=lantz.log.DEBUG) + + import time + import numpy as np + import warnings + #warnings.filterwarnings(action='ignore') + print(lantz.messagebased._resource_manager.list_resources()) + stage = SMC100('ASRL/dev/ttyUSB0::INSTR') + stage.initialize() + axis0 = stage.axes[0] + print('Axis id:' + axis0.idn) + print('Axis position: {}'.format(axis0.position)) + axis0.keypad_disable() + axis0.position += 0.1 * ureg.mm + print('Errors: {}'.format(axis0.get_errors())) + stage.finalize() + """ + + DEFAULTS = { + 'COMMON': {'write_termination': '\r\n', + 'read_termination': '\r\n', }, + 'ASRL': { + 'timeout': 100, # ms + 'encoding': 'ascii', + 'data_bits': 8, + 'baud_rate': 57600, + 'parity': constants.Parity.none, + 'stop_bits': constants.StopBits.one, + #'flow_control': constants.VI_ASRL_FLOW_NONE, + 'flow_control': constants.VI_ASRL_FLOW_XON_XOFF, # constants.VI_ASRL_FLOW_NONE, + }, + } + + def __init__(self, *args, **kwargs): + self.motionaxis_class = kwargs.pop('motionaxis_class', MotionAxisSMC100) + super().__init__(*args, **kwargs) + + def initialize(self): + super().initialize() + + # Clear read buffer + self.clear_read_buffer() + + self.detect_axis() + + @Action() + def clear_read_buffer(self): + '''Read all data that was still in the read buffer and discard this''' + try: + while True: + self.read() + except pyvisa.errors.VisaIOError: + pass # readbuffer was empty already + + @Action() + def detect_axis(self): + """ Find the number of axis available. + + The detection stops as soon as an empty controller is found. + """ + self.axes = [] + i = 0 + scan_axes = True + while scan_axes: + i += 1 + try: + idn = self.query('%dID?' % i) + except pyvisa.errors.VisaIOError: + scan_axes = False + else: + if idn == '': + scan_axes = False + else: + axis = self.motionaxis_class(self, i, idn) + self.axes.append(axis) + + + + +class MotionAxisSMC100(MotionAxis): + def query(self, command, *, send_args=(None, None), + recv_args=(None, None)): + respons = super().query(command, send_args=send_args, + recv_args=recv_args) + # check for command: + if not respons[:3] == '{:d}{}'.format(self.num, command[:2]): + self.log_error('Axis {}: Expected to return command {} instead of' + '{}'.format(self.num, command[:3], respons[:3])) + return respons[3:] + + def write(self, command, *args, **kwargs): + super().write(command, *args, **kwargs) + return self.get_errors() + + @Feat(units='mm') + def software_limit_positive(self): + '''Make sure that software limits are tighter than hardware limits, + else the stage will go to not reference mode''' + return self.query('SR?') + + @software_limit_positive.setter + def software_limit_positive(self, val): + return self._software_limit_setter(val, limit='positive') + + @Feat(units='mm') + def software_limit_negative(self): + return self.query('SL?') + + @software_limit_negative.setter + def software_limit_negative(self, val): + return self._software_limit_setter(val, limit='negative') + + def _software_limit_setter(self, val, limit='positive'): + self.enter_config_state() + if limit == 'positive': + ret = self.write('SR{}'.format(val)) + elif limit == 'negative': + ret = self.write('SL{}'.format(val)) + else: + self.log_error("Limit {} not in ('postive', 'negative')." + "".format(limit)) + self.leave_and_save_config_state() + return ret + + @Action() + def enter_config_state(self): + return self.write('PW1') + + @Action() + def leave_and_save_config_state(self): + '''Takes up to 10s, controller is unresposive in that time''' + super().write('PW0') + start = time.time() + # do-while loop + cont = True + while cont: + try: + self.status + except ValueError: + if (time.time() - start > 10): + self.log_error('Controller was going to CONFIGURATION ' + 'state but it took more than 10s. Trying ' + 'to continue anyway') + cont = False + else: + time.sleep(0.001) + else: + cont = False + + @Action() + def on(self): + """Put axis on""" + pass + self.write('MM1') + + @Action() + def off(self): + """Put axis off""" + pass + self.write('MM0') + + @Action() + def get_errors(self): + ret = self.query('TE?') + err = ERRORS.get(ret, 'Error {}. Lookup in manual: https://www.newpor' + 't.com/medias/sys_master/images/images/h11/he1/91171' + '82525470/SMC100CC-SMC100PP-User-s-Manual.pdf' + ''.format(ret)) + if err: + self.log_error('Axis {} error: {}'.format(self.num, err)) + return err + + @Feat() + def status(self): + '''Read and parse controller and axis status. This gives usefull error + messages''' + res = self.query('TS?') + positioner_error = [val for key, val in positioner_errors.items() if + int(res[:4], base=16) & key == key] + controller_state = controller_states[res[-2:]] + return positioner_error, controller_state + + @Feat(values={True: '1', False: '0'}) + def is_on(self): + """ + :return: True is axis on, else false + """ + return '1' + # return self.query('MM?') + + @Action() + def home(self): + super().home() + self._wait_until_done() + + def _wait_until_done(self): + er, st = self.status + if st == 'MOVING.': + time.sleep(self.wait_time) + return self._wait_until_done() + elif st[:5] == 'READY': + return True + else: + self.log_error('Not reached position. Controller state: {} ' + 'Positioner errors: {}' + ''.format(st, ','.join(er))) + return False + + @Feat() + def motion_done(self): + if self.status[1][:5] == 'READY': + return True + return False + + @Action() + def keypad_disable(self): + return self.write('JD') + + +if __name__ == '__main__': + import argparse + import lantz.log + + parser = argparse.ArgumentParser(description='Test SMC100 driver') + parser.add_argument('-p', '--port', type=str, default='1', + help='Serial port to connect to') + + args = parser.parse_args() + lantzlog = lantz.log.log_to_screen(level=lantz.log.INFO) + lantz.log.log_to_socket(lantz.log.DEBUG) + + import lantz + import visa + import lantz.drivers.newport_motion + sm = lantz.drivers.newport_motion.SMC100 + rm = visa.ResourceManager('@py') + lantz.messagebased._resource_manager = rm + + print(lantz.messagebased._resource_manager.list_resources()) + + with sm(args.port) as inst: + #with sm.via_serial(port=args.port) as inst: + inst.idn + # inst.initialize() # Initialize the communication with the power meter + # Find the status of all axes: + #for axis in inst.axes: + # print('Axis {} Position {} is_on {} max_velocity {} velocity {}'.format(axis.num, axis.position, + # axis.is_on, axis.max_velocity, + # axis.velocity)) diff --git a/lantz/drivers/pi/__init__.py b/lantz/drivers/pi/__init__.py new file mode 100644 index 0000000..320ab6a --- /dev/null +++ b/lantz/drivers/pi/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +""" + lantz.drivers.pi + ~~~~~~~~~~~~~~~~~~~~~~ + + :company: PhysikInstrumente + :description: Motion and positioning components + :website: https://www.physikinstrumente.com + + ---- + + :copyright: 2017 by Vasco Tenner + :license: BSD, see LICENSE for more details. +""" + +from .piezo import Piezo, parse_line, parse_multi + +__all__ = ['Piezo','parse_line','parse_multi'] diff --git a/lantz/drivers/pi/error_codes.py b/lantz/drivers/pi/error_codes.py new file mode 100644 index 0000000..c81e79b --- /dev/null +++ b/lantz/drivers/pi/error_codes.py @@ -0,0 +1,219 @@ +error_codes = {0: "PI_CNTR_NO_ERROR No error", +1: "PI_CNTR_PARAM_SYNTAX Parameter syntax error", +2: "PI_CNTR_UNKNOWN_COMMAND Unknown command", +3: "PI_CNTR_COMMAND_TOO_LONG Command length out of limits orcommand buffer overrun", +4: "PI_CNTR_SCAN_ERROR Error while scanning", +5: "PI_CNTR_MOVE_WITHOUT_REF_OR_NO_SERVO Unallowable move attempted onunreferenced axis, or move attemptedwith servo off", +6: "PI_CNTR_INVALID_SGA_PARAM Parameter for SGA not valid", +7: "PI_CNTR_POS_OUT_OF_LIMITS Position out of limits", +8: "PI_CNTR_VEL_OUT_OF_LIMITS Velocity out of limits", +9: "PI_CNTR_SET_PIVOT_NOT_POSSIBLE Attempt to set pivot point while U,V andW not all 0", +10: "PI_CNTR_STOP Controller was stopped by command", +11: "PI_CNTR_SST_OR_SCAN_RANGE Parameter for SST or for one of theembedded scan algorithms out of range", +12: "PI_CNTR_INVALID_SCAN_AXES Invalid axis combination for fast scan", +13: "PI_CNTR_INVALID_NAV_PARAM Parameter for NAV out of range", +14: "PI_CNTR_INVALID_ANALOG_INPUT Invalid analog channel", +15: "PI_CNTR_INVALID_AXIS_IDENTIFIER Invalid axis identifier", +16: "PI_CNTR_INVALID_STAGE_NAME Unknown stage name", +17: "PI_CNTR_PARAM_OUT_OF_RANGE Parameter out of range", +18: "PI_CNTR_INVALID_MACRO_NAME Invalid macro name", +19: "PI_CNTR_MACRO_RECORD Error while recording macro", +20: "PI_CNTR_MACRO_NOT_FOUND Macro not found", +21: "PI_CNTR_AXIS_HAS_NO_BRAKE Axis has no brake", +22: "PI_CNTR_DOUBLE_AXIS Axis identifier specified more than once", +23: "PI_CNTR_ILLEGAL_AXIS Illegal axis", +24: "PI_CNTR_PARAM_NR Incorrect number of parameters", +25: "PI_CNTR_INVALID_REAL_NR Invalid floating point number", +26: "PI_CNTR_MISSING_PARAM Parameter missing", +27: "PI_CNTR_SOFT_LIMIT_OUT_OF_RANGE Soft limit out of range", +28: "PI_CNTR_NO_MANUAL_PAD No manual pad found", +29: "PI_CNTR_NO_JUMP No more step-response values", +30: "PI_CNTR_INVALID_JUMP No step-response values recorded", +31: "PI_CNTR_AXIS_HAS_NO_REFERENCE Axis has no reference sensor", +32: "PI_CNTR_STAGE_HAS_NO_LIM_SWITCH Axis has no limit switch", +33: "PI_CNTR_NO_RELAY_CARD No relay card installed", +34: "PI_CNTR_CMD_NOT_ALLOWED_FOR_STAGE Command not allowed for selectedstage(s)", +35: "PI_CNTR_NO_DIGITAL_INPUT No digital input installed", +36: "PI_CNTR_NO_DIGITAL_OUTPUT No digital output configured", +37: "PI_CNTR_NO_MCM No more MCM responses", +38: "PI_CNTR_INVALID_MCM No MCM values recorded", +39: "PI_CNTR_INVALID_CNTR_NUMBER Controller number invalid", +40: "PI_CNTR_NO_JOYSTICK_CONNECTED No joystick configured", +41: "PI_CNTR_INVALID_EGE_AXIS Invalid axis for electronic gearing, axiscannot be slave", +42: "PI_CNTR_SLAVE_POSITION_OUT_OF_RANGE Position of slave axis is out of range", +43: "PI_CNTR_COMMAND_EGE_SLAVE Slave axis cannot be commandeddirectly when electronic gearing isenabled", +44: "PI_CNTR_JOYSTICK_CALIBRATION_FAILED Calibration of joystick failed", +45: "PI_CNTR_REFERENCING_FAILED Referencing failed", +46: "PI_CNTR_OPM_MISSING OPM (Optical Power Meter) missing", +47: "PI_CNTR_OPM_NOT_INITIALIZED OPM (Optical Power Meter) notinitialized or cannot be initialized", +48: "PI_CNTR_OPM_COM_ERROR OPM (Optical Power Meter)communication error", +49: "PI_CNTR_MOVE_TO_LIMIT_SWITCH_FAILED Move to limit switch failed", +50: "PI_CNTR_REF_WITH_REF_DISABLED Attempt to reference axis withreferencing disabled", +51: "PI_CNTR_AXIS_UNDER_JOYSTICK_CONTROL Selected axis is controlled by joystick", +52: "PI_CNTR_COMMUNICATION_ERROR Controller detected communicationerror", +53: "PI_CNTR_DYNAMIC_MOVE_IN_PROCESS MOV! motion still in progress", +54: "PI_CNTR_UNKNOWN_PARAMETER Unknown parameter", +55: "PI_CNTR_NO_REP_RECORDED No commands were recorded with REP", +56: "PI_CNTR_INVALID_PASSWORD Password invalid", +57: "PI_CNTR_INVALID_RECORDER_CHAN Data record table does not exist", +58: "PI_CNTR_INVALID_RECORDER_SRC_OPT Source does not exist; number too lowor too high", +59: "PI_CNTR_INVALID_RECORDER_SRC_CHAN Source record table number too low ortoo high", +60: "PI_CNTR_PARAM_PROTECTION Protected Param: Current CommandLevel (CCL) too low", +61: "PI_CNTR_AUTOZERO_RUNNING Command execution not possible whileautozero is running", +62: "PI_CNTR_NO_LINEAR_AXIS Autozero requires at least one linearaxis", +63: "PI_CNTR_INIT_RUNNING Initialization still in progress", +64: "PI_CNTR_READ_ONLY_PARAMETER Parameter is read-only", +65: "PI_CNTR_PAM_NOT_FOUND Parameter not found in nonvolatilememory", +66: "PI_CNTR_VOL_OUT_OF_LIMITS Voltage out of limits", +67: "PI_CNTR_WAVE_TOO_LARGE Not enough memory available forrequested wave curve", +68: "PI_CNTR_NOT_ENOUGH_DDL_MEMORY Not enough memory available for DDLtable; DDL cannot be started", +69: "PI_CNTR_DDL_TIME_DELAY_TOO_LARGE Time delay larger than DDL table; DDLcannot be started", +70: "PI_CNTR_DIFFERENT_ARRAY_LENGTH The requested arrays have differentlengths; query them separately", +71: "PI_CNTR_GEN_SINGLE_MODE_RESTART Attempt to restart the generator while itis running in single step mode", +72: "PI_CNTR_ANALOG_TARGET_ACTIVE Motion commands and wave generatoractivation are not allowed when analogtarget is active", +73: "PI_CNTR_WAVE_GENERATOR_ACTIVE Motion commands are not allowedwhen wave generator is active", +74: "PI_CNTR_AUTOZERO_DISABLED No sensor channel or no piezo channelconnected to selected axis (sensor andpiezo matrix)", +75: "PI_CNTR_NO_WAVE_SELECTED Generator started (WGO) withouthaving selected a wave table (WSL).", +76: "PI_CNTR_IF_BUFFER_OVERRUN Interface buffer overran and commandcouldn't be received correctly", +77: "PI_CNTR_NOT_ENOUGH_RECORDED_DATA Data record table does not hold enoughrecorded data", +78: "PI_CNTR_TABLE_DEACTIVATED Data record table is not configured forrecording", +79: "PI_CNTR_OPENLOOP_VALUE_SET_WHEN_SERVO_ON Open-loop commands (SVA, SVR) arenot allowed when servo is on", +80: "PI_CNTR_RAM_ERROR Hardware error affecting RAM", +81: "PI_CNTR_MACRO_UNKNOWN_COMMAND Not macro command", +82: "PI_CNTR_MACRO_PC_ERROR Macro counter out of range", +83: "PI_CNTR_JOYSTICK_ACTIVE Joystick is active", +84: "PI_CNTR_MOTOR_IS_OFF Motor is off", +85: "PI_CNTR_ONLY_IN_MACRO Macro-only command", +86: "PI_CNTR_JOYSTICK_UNKNOWN_AXISInvalid joystick axis", +87: "PI_CNTR_JOYSTICK_UNKNOWN_IDJoystick unknown", +88: "PI_CNTR_REF_MODE_IS_ONMove without referenced stage", +89: "PI_CNTR_NOT_ALLOWED_IN_CURRENT_MOTION_MODE Command not allowed in current motionmode", +90: "PI_CNTR_DIO_AND_TRACING_NOT_POSSIBLENo tracing possible while digital IOs areused on this HW revision. Reconnect toswitch operation mode.", +91: "PI_CNTR_COLLISIONMove not possible, would causecollision", +92: "PI_CNTR_SLAVE_NOT_FAST_ENOUGH Stage is not capable of following themaster. Check the gear ratio.", +93: "PI_CNTR_CMD_NOT_ALLOWED_WHILE_AXIS_IN_MOTION This command is not allowed while theaffected axis or its master is in motion.", +94: "PI_CNTR_OPEN_LOOP_JOYSTICK_ENABLED Servo cannot be switched on whenopen-loop joystick control is enabled.", +95: "PI_CNTR_INVALID_SERVO_STATE_FOR_PARAMETER This parameter cannot be changed incurrent servo mode.", +96: "PI_CNTR_UNKNOWN_STAGE_NAME Unknown stage name", +100: "PI_LABVIEW_ERROR PI LabVIEW driver reports error. Seesource control for details.", +200: "PI_CNTR_NO_AXIS No stage connected to axis", +201: "PI_CNTR_NO_AXIS_PARAM_FILE File with axis parameters not found", +202: "PI_CNTR_INVALID_AXIS_PARAM_FILE Invalid axis parameter file", +203: "PI_CNTR_NO_AXIS_PARAM_BACKUP Backup file with axis parameters notfound", +204: "PI_CNTR_RESERVED_204 PI internal error code 204", +205: "PI_CNTR_SMO_WITH_SERVO_ON SMO with servo on", +206: "PI_CNTR_UUDECODE_INCOMPLETE_HEADER uudecode: incomplete header", +207: "PI_CNTR_UUDECODE_NOTHING_TO_DECODE uudecode: nothing to decode", +208: "PI_CNTR_UUDECODE_ILLEGAL_FORMAT uudecode: illegal UUE format", +209: "PI_CNTR_CRC32_ERROR CRC32 error", +210: "PI_CNTR_ILLEGAL_FILENAME Illegal file name (must be 8-0 format)", +211: "PI_CNTR_FILE_NOT_FOUND File not found on controller", +212: "PI_CNTR_FILE_WRITE_ERROR Error writing file on controller", +213: "PI_CNTR_DTR_HINDERS_VELOCITY_CHANGE VEL command not allowed in DTRcommand mode", +214: "PI_CNTR_POSITION_UNKNOWN Position calculations failed", +215: "PI_CNTR_CONN_POSSIBLY_BROKEN The connection between controller andstage may be broken", +216: "PI_CNTR_ON_LIMIT_SWITCH The connected stage has driven into alimit switch, some controllers need CLRto resume operation", +217: "PI_CNTR_UNEXPECTED_STRUT_STOP Strut test command failed because ofan unexpected strut stop", +218: "PI_CNTR_POSITION_BASED_ON_ESTIMATION While MOV! is running position can onlybe estimated!", +219: "PI_CNTR_POSITION_BASED_ON_INTERPOLATION Position was calculated during MOVmotion", +230: "PI_CNTR_INVALID_HANDLE Invalid handle", +231: "PI_CNTR_NO_BIOS_FOUND No bios found", +232: "PI_CNTR_SAVE_SYS_CFG_FAILED Save system configuration failed", +233: "PI_CNTR_LOAD_SYS_CFG_FAILED Load system configuration failed", +301: "PI_CNTR_SEND_BUFFER_OVERFLOW Send buffer overflow", +302: "PI_CNTR_VOLTAGE_OUT_OF_LIMITS Voltage out of limits", +303: "PI_CNTR_OPEN_LOOP_MOTION_SET_WHEN_SERVO_ON Open-loop motion attempted whenservo ON", +304: "PI_CNTR_RECEIVING_BUFFER_OVERFLOW Received command is too long", +305: "PI_CNTR_EEPROM_ERROR Error while reading/writing EEPROM", +306: "PI_CNTR_I2C_ERROR Error on I2C bus", +307: "PI_CNTR_RECEIVING_TIMEOUT Timeout while receiving command", +308: "PI_CNTR_TIMEOUT A lengthy operation has not finished inthe expected time", +309: "PI_CNTR_MACRO_OUT_OF_SPACE Insufficient space to store macro", +310: "PI_CNTR_EUI_OLDVERSION_CFGDATA Configuration data has old versionnumber", +311: "PI_CNTR_EUI_INVALID_CFGDATA Invalid configuration data", +333: "PI_CNTR_HARDWARE_ERROR Internal hardware error", +400: "PI_CNTR_WAV_INDEX_ERROR Wave generator index error", +401: "PI_CNTR_WAV_NOT_DEFINED Wave table not defined", +402: "PI_CNTR_WAV_TYPE_NOT_SUPPORTED Wave type not supported", +403: "PI_CNTR_WAV_LENGTH_EXCEEDS_LIMIT Wave length exceeds limit", +404: "PI_CNTR_WAV_PARAMETER_NR Wave parameter number error", +405: "PI_CNTR_WAV_PARAMETER_OUT_OF_LIMIT Wave parameter out of range", +406: "PI_CNTR_WGO_BIT_NOT_SUPPORTED WGO command bit not supported", +500: "PI_CNTR_EMERGENCY_STOP_BUTTON_ACTIVATED The \"red knob\" is still set and disablessystem", +501: "PI_CNTR_EMERGENCY_STOP_BUTTON_WAS_ACTIVATE The \"red knob\" was activated and stillDdisables system - reanimation required", +502: "PI_CNTR_REDUNDANCY_LIMIT_EXCEEDED Position consistency check failed", +503: "PI_CNTR_COLLISION_SWITCH_ACTIVATED Hardware collision sensor(s) areactivated", +504: "PI_CNTR_FOLLOWING_ERROR Strut following error occurred, e.g.caused by overload or encoder failure", +505: "PI_CNTR_SENSOR_SIGNAL_INVALID One sensor signal is not valid", +506: "PI_CNTR_SERVO_LOOP_UNSTABLE Servo loop was unstable due to wrongparameter setting and switched off toavoid damage.", +530: "PI_CNTR_NODE_DOES_NOT_EXIST A command refers to a node that doesnot exist", +531: "PI_CNTR_PARENT_NODE_DOES_NOT_EXIST A command refers to a node that hasno parent node", +532: "PI_CNTR_NODE_IN_USE Attempt to delete a node that is in use", +533: "PI_CNTR_NODE_DEFINITION_IS_CYCLIC Definition of a node is cyclic", +534: "PI_CNTR_NODE_CHAIN_INVALID The node chain does not end in the \"0\"node", +535: "PI_CNTR_NODE_DEFINITION_NOT_CONSISTENT The definition of a coordinatetransformation is erroneous", +536: "PI_CNTR_HEXAPOD_IN_MOTION Transformation cannot be defined aslong as Hexapod is in motion", +537: "PI_CNTR_TRANSFORMATION_TYPE_NOT_SUPPORTED Transformation node cannot beactivated", +538: "PI_CNTR_NODE_TYPE_DIFFERS A node can only be replaced by a nodeof the same type", +539: "PI_CNTR_NODE_PARENT_IDENTICAL_TO_CHILD A node cannot be linked to itself", +540: "PI_CNTR_NODE_DEFINITION_INCONSISTENT Node definition is erroneous or notcomplete (replace or delete it)", +541: "PI_CNTR_ZERO_NODE_CANNOT_BE_CHANGED_OR_REPLACED 0 is the root node and cannot bemodified", +542: "PI_CNTR_NODES_NOT_IN_SAME_CHAIN The nodes are not part of the samechain", +543: "PI_CNTR_NODE_MEMORY_FULL Unused nodes must be deleted beforenew nodes can be stored", +544: "PI_CNTR_PIVOT_POINT_FEATURE_NOT_SUPPORTED With some transformations pivot pointusage is not supported", +555: "PI_CNTR_UNKNOWN_ERROR BasMac: unknown controller error", +601: "PI_CNTR_NOT_ENOUGH_MEMORY Not enough memory", +602: "PI_CNTR_HW_VOLTAGE_ERROR Hardware voltage error", +603: "PI_CNTR_HW_TEMPERATURE_ERROR Hardware temperature out of range", +604: "PI_CNTR_POSITION_ERROR_TOO_HIGH Position error of any axis in the systemis too high", +606: "PI_CNTR_INPUT_OUT_OF_RANGE Maximum value of input signal hasbeen exceeded", +1000: "PI_CNTR_TOO_MANY_NESTED_MACROS Too many nested macros", +1001: "PI_CNTR_MACRO_ALREADY_DEFINED Macro already defined", +1002: "PI_CNTR_NO_MACRO_RECORDING Macro recording not activated", +1003: "PI_CNTR_INVALID_MAC_PARAM Invalid parameter for MAC", +1004: "PI_CNTR_RESERVED_1004 PI internal error code 1004", +1005: "PI_CNTR_CONTROLLER_BUSY Controller is busy with some lengthyoperation (e.g. reference move, fastscan algorithm)", +1006: "PI_CNTR_INVALID_IDENTIFIER Invalid identifier (invalid specialcharacters, ...)", +1007: "PI_CNTR_UNKNOWN_VARIABLE_OR_ARGUMENT Variable or argument not defined", +1008: "PI_CNTR_RUNNING_MACRO Controller is (already) running a macro", +1009: "PI_CNTR_MACRO_INVALID_OPERATOR Invalid or missing operator for condition.Check necessary spaces aroundoperator.", +1010: "PI_CNTR_MACRO_NO_ANSWER No answer was received whileexecuting WAC/MEX/JRC/...", +1011: "PI_CMD_NOT_VALID_IN_MACRO_MODE Command not valid during macroexecution", +1024: "PI_CNTR_MOTION_ERROR Motion error: position error too large,servo is switched off automatically", +1063: "PI_CNTR_EXT_PROFILE_UNALLOWED_CMD User profile mode: command is notallowed, check for required preparatorycommands", +1064: "PI_CNTR_EXT_PROFILE_EXPECTING_MOTION_ERROR User profile mode: first target position inuser profile is too far from currentposition", +1065: "PI_CNTR_PROFILE_ACTIVE Controller is (already) in user profilemode", +1066: "PI_CNTR_PROFILE_INDEX_OUT_OF_RANGE User profile mode: block or data setindex out of allowed range", +1071: "PI_CNTR_PROFILE_OUT_OF_MEMORY User profile mode: out of memory", +1072: "PI_CNTR_PROFILE_WRONG_CLUSTER User profile mode: cluster is notassigned to this axis", +1073: "PI_CNTR_PROFILE_UNKNOWN_CLUSTER_IDENTIFIER Unknown cluster identifier", +1090: "PI_CNTR_TOO_MANY_TCP_CONNECTIONS_OPEN There are too many open tcpipconnections", +2000: "PI_CNTR_ALREADY_HAS_SERIAL_NUMBER Controller already has a serial number", +4000: "PI_CNTR_SECTOR_ERASE_FAILED Sector erase failed", +4001: "PI_CNTR_FLASH_PROGRAM_FAILED Flash program failed", +4002: "PI_CNTR_FLASH_READ_FAILED Flash read failed", +4003: "PI_CNTR_HW_MATCHCODE_ERROR HW match code missing/invalid", +4004: "PI_CNTR_FW_MATCHCODE_ERROR FW match code missing/invalid", +4005: "PI_CNTR_HW_VERSION_ERROR HW version missing/invalid", +4006: "PI_CNTR_FW_VERSION_ERROR FW version missing/invalid", +4007: "PI_CNTR_FW_UPDATE_ERROR FW update failed", +5000: "PI_CNTR_INVALID_PCC_SCAN_DATA PicoCompensation scan data is notvalid", +5001: "PI_CNTR_PCC_SCAN_RUNNING PicoCompensation is running, someactions cannot be executed duringscanning/recording", +5002: "PI_CNTR_INVALID_PCC_AXIS Given axis cannot be defined as PPCaxis", +5003: "PI_CNTR_PCC_SCAN_OUT_OF_RANGE Defined scan area is larger than thetravel range", +5004: "PI_CNTR_PCC_TYPE_NOT_EXISTING Given PicoCompensation type is notdefined", +5005: "PI_CNTR_PCC_PAM_ERROR PicoCompensation parameter error", +5006: "PI_CNTR_PCC_TABLE_ARRAY_TOO_LARGE PicoCompensation table is larger thanmaximum table length", +5100: "PI_CNTR_NEXLINE_ERROR Common error in NEXLINE® firmwaremodule", +5101: "PI_CNTR_CHANNEL_ALREADY_USED Output channel for NEXLINE® cannotbe redefined for other usage", +5102: "PI_CNTR_NEXLINE_TABLE_TOO_SMALL Memory for NEXLINE® signals is toosmall", +5103: "PI_CNTR_RNP_WITH_SERVO_ON RNP cannot be executed if axis is inclosed loop", +5104: "PI_CNTR_RNP_NEEDED Relax procedure (RNP) needed", +5200: "PI_CNTR_AXIS_NOT_CONFIGURED Axis must be configured for this action", +6000: "PI_CNTR_SENSOR_ABS_INVALID_VALUE Invalid preset value of absolute sensor", +6001: "PI_CNTR_SENSOR_ABS_WRITE_ERROR Error while writing to sensor", +6002: "PI_CNTR_SENSOR_ABS_READ_ERROR Error while reading from sensor", +6003: "PI_CNTR_SENSOR_ABS_CRC_ERROR Checksum error of absolute sensor", +6004: "PI_CNTR_SENSOR_ABS_ERROR General error of absolute sensor", +6005: "PI_CNTR_SENSOR_ABS_OVERFLOW Overflow of absolute sensor position", +} diff --git a/lantz/drivers/pi/piezo.py b/lantz/drivers/pi/piezo.py new file mode 100644 index 0000000..6349835 --- /dev/null +++ b/lantz/drivers/pi/piezo.py @@ -0,0 +1,270 @@ +""" + lantz.drivers.pi.piezo + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Implements the drivers to control pi E-709 piezo motion controller + via USB or via serial. It uses the PI General Command Set. + + For USB, one first have to install the driver from PI. + :copyright: 2018, see AUTHORS for more details. + :license: GPL, see LICENSE for more details. + + Source: Instruction Manual (PI) +""" + +from lantz.feat import Feat +from lantz.action import Action +from lantz.messagebased import MessageBasedDriver +# from pyvisa import constants +from lantz import Q_, ureg +# from lantz.processors import convert_to +import time +import numpy as np +# import copy +from collections import OrderedDict + +if __name__=='__main__': + from error_codes import error_codes +else: + from .error_codes import error_codes + + +def parse_line(line): + line = line.strip() # remove whitespace at end + return line.split('=') + + +def parse_multi(message): + '''Return an ordered dictionary containing the returned parameters''' + assert isinstance(message, list) + return OrderedDict([parse_line(line) for line in message]) + + +class Piezo(MessageBasedDriver): + """ PI piezo motion controller. It assumes all axes to have units um + + Important: + before operating, ensure that the notch filters are set to the right + frequencies. These frequencies depend on the load on the piezo, and can + be determined in NanoCapture. + + Example: + import numpy as np + import lantz + import visa + import lantz.drivers.pi.piezo as pi + rm = visa.ResourceManager('@py') + lantz.messagebased._resource_manager = rm + ureg = lantz.ureg + try: + lantzlog + except NameError: + #lantzlog = lantz.log.log_to_screen(level=lantz.log.INFO) + lantzlog = lantz.log.log_to_screen(level=lantz.log.ERROR) + + import warnings + warnings.filterwarnings(action='ignore') + + print('Available devices:', lantz.messagebased._resource_manager.list_resources()) + print('Connecting to: ASRL/dev/ttyUSB0::INSTR') + stage = pi.Piezo('ASRL/dev/ttyUSB0::INSTR', axis='X', + sleeptime_after_move=10*ureg.ms) + stage.initialize() + idn = stage.idn + + stage.servo = True + stage.velocity = 10*ureg.um/ureg.ms + + stage.position = 0*ureg.um + print('stage position measured: ', stage.position) + + steps = 10 + stepsize = 100.0*ureg.nm + for n in range(steps): + stage.position = n*stepsize + print('stage position measured: ', stage.position) + + print('Measuring step response') + + # For jupyter notebooks + %matplotlib inline + + stage.servo = True + stage.position = 0 + import time + time.sleep(1) + + stepsize = 10*lantz.ureg.um + timepoints = 100 + + timepos = stage.measure_step_response(stepsize, timepoints) + + lines = stage.plot_step_response(timepos) + + stage.finalize() # nicely close stage and turn of servo + + + """ + + DEFAULTS = {'COMMON': {'write_termination': '\n', + 'read_termination': '\n', + 'baud_rate': 57600, + 'timeout': 20}, } + + def __init__(self, *args, **kwargs): + self.sleeptime_after_move = kwargs.pop('sleeptime_after_move', 0*ureg.ms) + self.axis = kwargs.pop('axis', 'X') + super().__init__(*args, **kwargs) + + def initialize(self): + super().initialize() + + def finalize(self): + """ Disconnects stage """ + self.stop() + super().finalize() + + def query_multiple(self, query): + """Read a multi line response""" + self.write(query) + loop = True + ans = [] + while loop: + ret = self.read() + if ret != '': + ans.append(ret) + else: + return ans + + def parse_multiaxis(self, message, axis=None): + "Parse a multi-axis message, return only value for self.axis" + if axis is None: + axis = self.axis + message_parts = message.split(' ') + try: + message_axis = [part for part in message_parts if part[0] == axis][0] + except IndexError: + self.log_error('Axis {} not found in returned message: {}'.format(axis, message)) + values = message_axis.split('=') + return values[-1] + + @Feat() + def errors(self): + error = int(self.query('ERR?')) + if error != 0: + self.log_error('Stage error code {}: {}'.format(error, error_codes[error])) + return error + + @Feat() + def idn(self): + idn = self.query("*IDN?") + self.errors + return idn + + @Action() + def stop(self): + '''Stop all motions''' + self.servo = False + self.write('STP') + self.errors + + @Feat(values={True: '1', False: '0'}, read_once=True) + def servo(self): + ''' Set the stage control in open- or closed-loop (state = False or True)''' + return self.parse_multiaxis(self.query('SVO?')) + + @servo.setter + def servo(self, state): + self.write('SVO {} {}'.format(self.axis, state) ) + return self.errors + + @Feat(units='um/s') + def velocity(self): + ''' Set the stage velocity (closed-loop only)''' + return self.parse_multiaxis(self.query('VEL?')) + + @velocity.setter + def velocity(self, velocity): + self.write('VEL {} {}'.format(self.axis, velocity)) + return self.errors + + @Feat(units='um') + def position(self): + ''' Move to an absolute position the stage (closed-loop only)''' + return self.parse_multiaxis(self.query('POS?')) + + @position.setter + def position(self, position): + return self.move_to(position, self.sleeptime_after_move) + + @Action(units=('um','ms')) + def move_to(self, position, timeout=None): + ''' Move to an absolute position the stage (closed-loop only)''' + self.write('MOV {} {}'.format(self.axis, position)) + time.sleep(timeout * 1e-3) # Give the stage time to move! (in seconds!) + return self.errors + + @Feat(units='um') + def read_stage_position(self, nr_avg = 1): + ''' Read the current position from the stage''' + positions = [self.position.magnitude for n in range(nr_avg)] + return np.mean(positions) + + @Action(units=('um',None)) + def measure_step_response(self, stepsize, points): + '''Python implementation to measure a step response of size stepsize. + Measure number of points as fast as possible. + + Servo should be on + + Note: a higher temporal resolution can be aquired by the data-recorder + in the driver + + Example: + stage.servo = True + stage.position = 0 + time.sleep(1) + + stepsize = 10*lantz.ureg.um + timepoints = 100 + + timepos = stage.measure_step_response(stepsize, timepoints) + + lines = stage.plot_step_response(timepos) + ''' + if not self.servo: + self.log.error('Servo should be on') + return + import time + self.move_to(self.position + stepsize*ureg.um, 0) + timepos = np.array([[time.time(), self.position.magnitude] for i in range(points)]) + timepos[:,0] -= timepos[0,0] #correct time offset + timepos[:,0] *= 1000 # make it milliseconds + + return timepos + + def plot_step_response(self, timepos): + '''Helper function to visualize a step respons''' + import matplotlib.pyplot as plt + lines = plt.plot(*timepos.T) + plt.xlabel('Time [ms]') + plt.ylabel('Position [um]') + return lines + +if __name__=='__main__': + # before operating, ensure that the notch filters are set to the right + # frequencies. These frequencies depend on the load on the piezo, and can + # be determined in NanoCapture. + from lantz.ui.app import start_test_app + + import lantz + import visa + import lantz.drivers.pi.piezo as pi + rm = visa.ResourceManager('@py') + lantz.messagebased._resource_manager = rm + try: + lantzlog + except NameError: + lantzlog = lantz.log.log_to_screen(level=lantz.log.INFO) + with Piezo('ASRL/dev/ttyUSB0::INSTR') as inst: + start_test_app(inst) + diff --git a/lantz/drivers/princetoninstruments/lightfield.py b/lantz/drivers/princetoninstruments/lightfield.py index b807cb3..cc0c9d2 100644 --- a/lantz/drivers/princetoninstruments/lightfield.py +++ b/lantz/drivers/princetoninstruments/lightfield.py @@ -1,28 +1,32 @@ import numpy as np from time import sleep -import clr + import sys import os -# Import DLLs for running spectrometer via LightField -lf_root = os.environ['LIGHTFIELD_ROOT'] -automation_path = lf_root + '\PrincetonInstruments.LightField.AutomationV4.dll' -addin_path = lf_root + '\AddInViews\PrincetonInstruments.LightFieldViewV4.dll' -support_path = lf_root + '\PrincetonInstruments.LightFieldAddInSupportServices.dll' - -addin_class = clr.AddReference(addin_path); -automation_class = clr.AddReference(automation_path); -support_class = clr.AddReference(support_path); - -import PrincetonInstruments.LightField as lf - -# Import some system functions for interfacing with LightField code -clr.AddReference("System.Collections") -clr.AddReference("System.IO") -from System.Collections.Generic import List -from System import String -from System.IO import FileAccess +try: + import clr + # Import DLLs for running spectrometer via LightField + lf_root = os.environ['LIGHTFIELD_ROOT'] + automation_path = lf_root + '\PrincetonInstruments.LightField.AutomationV4.dll' + addin_path = lf_root + '\AddInViews\PrincetonInstruments.LightFieldViewV4.dll' + support_path = lf_root + '\PrincetonInstruments.LightFieldAddInSupportServices.dll' + + addin_class = clr.AddReference(addin_path); + automation_class = clr.AddReference(automation_path); + support_class = clr.AddReference(support_path); + + import PrincetonInstruments.LightField as lf + + # Import some system functions for interfacing with LightField code + clr.AddReference("System.Collections") + clr.AddReference("System.IO") + from System.Collections.Generic import List + from System import String + from System.IO import FileAccess +except: + pass # Lantz imports from lantz import Driver, Feat, DictFeat, Action diff --git a/lantz/drivers/sacher/EposCmd.dll b/lantz/drivers/sacher/EposCmd.dll new file mode 100644 index 0000000..8865e3c Binary files /dev/null and b/lantz/drivers/sacher/EposCmd.dll differ diff --git a/lantz/drivers/sacher/Sacher.py b/lantz/drivers/sacher/Sacher.py new file mode 100644 index 0000000..40ebcd8 --- /dev/null +++ b/lantz/drivers/sacher/Sacher.py @@ -0,0 +1,697 @@ +#Sacher_playground.py +""" +Make sure you're in the lantz32 environment to run this code properly +""" + +import visa +import types +import ctypes +import sys +import numpy as np +import logging +from ctypes.wintypes import DWORD +from ctypes.wintypes import WORD +import ctypes.wintypes +import time +import os + +from lantz.foreign import LibraryDriver +from lantz import Feat + +directory = os.path.dirname(os.path.realpath(__file__)) + +DeviceName = b'EPOS' +ProtocolStackName = b'MAXON_RS232' +InterfaceName = b'RS232' + + +""" + ok before I dive into this giant Sacher class thing let me just list here all the functions that are being defined in this class: + + These are the important ones: + 1) __init__(self, name, address, reset=False) + 2) open(self) + 3) initialize(self) + 4) get_offset(self) + 5) get_motor_position(self) + 6) do_get_wavelength(self) + 7) set_new_offset(self, new_offset) + 8) set_coeffs(self, a, b, c, min_wl, max_wl) + 9) do_set_wavelength(self, wavelength) + 10) set_target_position(self, target, absolute, immediately) + + And then there are these random ones: + 11) get_bit(self, byteval,idx) + 12) _u32todouble(self, uinput) + 13) _doubletou32(dinput) + 14) __del__(self) + 15) close(self) + 16) get_motor_current(self) + 17) find_home(self) + 18) restore(self) + 19) fine_tuning_steps(self, steps) + 20) is_open(self) + 21) clear_fault(self) + + They're labeled as #1, #2, ..., so you can find them with some ctrl+f + """ + + +class Sacher_EPOS(LibraryDriver): + LIBRARY_NAME = 'EposCmd.dll' + LIBRARY_PREFIX = 'VCS_' +#1) + def __init__(self, name, address, reset=False): + super().__init__() + self._port_name = address + self._is_open = False + self._HPM = True + self.open() + self.initialize() + """ + This is the special "__init__" function that gets runs automatically + """ + +#2) + def open(self): + self.lib.OpenDevice.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.POINTER(DWORD)] + self.lib.OpenDevice.restype = ctypes.wintypes.HANDLE + buf = ctypes.pointer(DWORD(0)) + ret = ctypes.wintypes.HANDLE() + ret = self.lib.OpenDevice(DeviceName, ProtocolStackName, InterfaceName, self._port_name, buf) + self._keyhandle = ret + if int(buf.contents.value) >= 0: + self._is_open = True + self._keyhandle = ret + return + +#3) + """ + This uses u32todouble three times, for the three coefficients + + Okay here's the important part: + self._coefA = CastedObjectData[0] + + self._doubleA = self._u32todouble(self._coefA) + self._doubleB = self._u32todouble(self._coefB) + self._doubleC = self._u32todouble(self._coefC) + self._currentwl = self._doubleA*(self._offset)**2.0 + self._doubleB*self._offset + self._doubleC + + """ + def initialize(self): + nodeID = ctypes.wintypes.WORD(0) + buf = ctypes.wintypes.DWORD(0) + BaudRate = DWORD(38400) + Timeout = DWORD(100) + ret = self.lib.SetProtocolStackSettings(self._keyhandle,BaudRate,Timeout,ctypes.byref(buf)) + if ret == 0: + errbuf = ctypes.create_string_buffer(64) + raise ValueError(errbuf.value) + buf = ctypes.wintypes.DWORD(0) + ret = self.lib.ClearFault(self._keyhandle,nodeID,ctypes.byref(buf)) + if ret == 0: + errbuf = ctypes.create_string_buffer(64) + self.lib.GetErrorInfo(buf, errbuf, WORD(64)) + raise ValueError(errbuf.value) + buf = ctypes.wintypes.DWORD(0) + plsenabled = ctypes.wintypes.DWORD(0) + ret = self.lib.GetEnableState(self._keyhandle,nodeID,ctypes.byref(plsenabled),ctypes.byref(buf)) + if ret == 0: + errbuf = ctypes.create_string_buffer(64) + self.lib.GetErrorInfo(buf, errbuf, WORD(64)) + raise ValueError(errbuf.value) + if int(plsenabled.value) != 0: + logging.warning(__name__ + ' EPOS motor enabled, disabling before proceeding.') + ret = self.lib.SetDisableState(self._keyhandle,nodeID,ctypes.byref(buf)) + if int(ret) != 0: + logging.warning(__name__ + ' EPOS motor successfully disabled, proceeding') + else: + logging.error(__name__ + ' EPOS motor was not successfully disabled!') + buf = ctypes.wintypes.DWORD(0) + Counts = WORD(512) # incremental encoder counts in pulses per turn + PositionSensorType = WORD(4) + ret = self.lib.SetEncoderParameter(self._keyhandle,nodeID,Counts,PositionSensorType,ctypes.byref(buf)) + + # Get operation mode, check if it's 1 -- this is "profile position mode" + buf = ctypes.wintypes.DWORD(0) + pMode = ctypes.pointer(ctypes.c_int8()) + self.lib.GetOperationMode.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.POINTER(ctypes.c_int8), ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.GetOperationMode.restype = ctypes.wintypes.BOOL + ret = self.lib.GetOperationMode(self._keyhandle, nodeID, pMode, ctypes.byref(buf)) + # if mode is not 1, make it 1 + if pMode.contents.value != 1: + self.lib.SetOperationMode.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.c_int8, ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.SetOperationMode.restype = ctypes.wintypes.BOOL + pMode_setting = ctypes.c_int8(1) + ret = self.lib.SetOperationMode(self._keyhandle, nodeID, pMode_setting, ctypes.byref(buf)) + self.lib.GetPositionProfile.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.GetPositionProfile.restype = ctypes.wintypes.BOOL + pProfileVelocity = ctypes.pointer(ctypes.wintypes.DWORD()) + pProfileAcceleration = ctypes.pointer(ctypes.wintypes.DWORD()) + pProfileDeceleration = ctypes.pointer(ctypes.wintypes.DWORD()) + ret = self.lib.GetPositionProfile(self._keyhandle, nodeID, pProfileVelocity, pProfileAcceleration, pProfileDeceleration,ctypes.byref(buf)) + + #print(pProfileVelocity.contents.value, pProfileAcceleration.contents.value, pProfileDeceleration.contents.value) + + if (int(pProfileVelocity.contents.value) > int(11400) or int(pProfileAcceleration.contents.value) > int(60000) or int(pProfileDeceleration.contents.value) > int(60000)): + self.lib.GetPositionProfile.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.GetPositionProfile.restype = ctypes.wintypes.BOOL + pProfileVelocity = ctypes.wintypes.DWORD(429) + pProfileAcceleration = ctypes.wintypes.DWORD(429) + pProfileDeceleration = ctypes.wintypes.DWORD(429) + logging.warning(__name__ + ' GetPositionProfile out of bounds, resetting...') + ret = self.lib.SetPositionProfile(self._keyhandle, nodeID, pProfileVelocity, pProfileAcceleration, pProfileDeceleration,ctypes.byref(buf)) + + self._offset = self.get_offset() + + """DC - These are hardcoded values I got from the LabVIEW program -- I don't think any documentation exists on particular object indices""" + + """Coefficient A""" + self.lib.GetObject.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.GetObject.restype = ctypes.wintypes.BOOL + StoredPositionObject = ctypes.wintypes.WORD(8204) + StoredPositionObjectSubindex = ctypes.c_uint8(1) + StoredPositionNbBytesToRead = ctypes.wintypes.DWORD(4) + ObjectData = ctypes.c_void_p() + ObjectDataArray = (ctypes.c_uint32*1)() + ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_uint32)) + StoredPositionNbBytesRead = ctypes.pointer(ctypes.wintypes.DWORD(0)) + ret = self.lib.GetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToRead, StoredPositionNbBytesRead, ctypes.byref(buf)) + # Cast the object data to uint32 + CastedObjectData = ctypes.cast(ObjectData, ctypes.POINTER(ctypes.c_uint32)) + self._coefA = CastedObjectData[0] + + """Coefficient B""" + self.lib.GetObject.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.GetObject.restype = ctypes.wintypes.BOOL + StoredPositionObject = ctypes.wintypes.WORD(8204) + StoredPositionObjectSubindex = ctypes.c_uint8(2) + StoredPositionNbBytesToRead = ctypes.wintypes.DWORD(4) + ObjectData = ctypes.c_void_p() + ObjectDataArray = (ctypes.c_uint32*1)() + ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_uint32)) + StoredPositionNbBytesRead = ctypes.pointer(ctypes.wintypes.DWORD(0)) + ret = self.lib.GetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToRead, StoredPositionNbBytesRead, ctypes.byref(buf)) + # Cast the object data to uint32 + CastedObjectData = ctypes.cast(ObjectData, ctypes.POINTER(ctypes.c_uint32)) + self._coefB = CastedObjectData[0] + + """Coefficient C""" + self.lib.GetObject.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.GetObject.restype = ctypes.wintypes.BOOL + StoredPositionObject = ctypes.wintypes.WORD(8204) + StoredPositionObjectSubindex = ctypes.c_uint8(3) + StoredPositionNbBytesToRead = ctypes.wintypes.DWORD(4) + ObjectData = ctypes.c_void_p() + ObjectDataArray = (ctypes.c_uint32*1)() + ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_uint32)) + StoredPositionNbBytesRead = ctypes.pointer(ctypes.wintypes.DWORD(0)) + ret = self.lib.GetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToRead, StoredPositionNbBytesRead, ctypes.byref(buf)) + # Cast the object data to uint32 + CastedObjectData = ctypes.cast(ObjectData, ctypes.POINTER(ctypes.c_uint32)) + self._coefC = CastedObjectData[0] + + """Coefficient D""" + self.lib.GetObject.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.GetObject.restype = ctypes.wintypes.BOOL + StoredPositionObject = ctypes.wintypes.WORD(8204) + StoredPositionObjectSubindex = ctypes.c_uint8(4) + StoredPositionNbBytesToRead = ctypes.wintypes.DWORD(4) + ObjectData = ctypes.c_void_p() + ObjectDataArray = (ctypes.c_uint32*1)() + ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_uint32)) + StoredPositionNbBytesRead = ctypes.pointer(ctypes.wintypes.DWORD(0)) + ret = self.lib.GetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToRead, StoredPositionNbBytesRead, ctypes.byref(buf)) + # Cast the object data to uint32 + CastedObjectData = ctypes.cast(ObjectData, ctypes.POINTER(ctypes.c_uint32)) + self._coefD = CastedObjectData[0] + + """ + print('coefficients are %s %s %s %s' % (self._coefA, self._coefB, self._coefC, self._coefD)) + This gives the coefficients in some weird form, they're not what you expect them to be + """ + + self._doubleA = self._u32todouble(self._coefA) + self._doubleB = self._u32todouble(self._coefB) + self._doubleC = self._u32todouble(self._coefC) + firstHalf = np.int16(self._coefD >> 16) + secondHalf = np.int16(self._coefD & 0xffff) + # Set the minimum and maximum wavelengths for the motor + self._minwl = float(firstHalf)/10.0 + self._maxwl = float(secondHalf)/10.0 + # print 'first %s second %s' % (firstHalf, secondHalf) + # This returns '10871' and '11859' for the Sacher, which are the correct + # wavelength ranges in Angstroms + #print 'Now calculate the current wavelength position:' + self._currentwl = self._doubleA*(self._offset)**2.0 + self._doubleB*self._offset + self._doubleC + #print('Current wavelength: %.3f nm' % self._currentwl) + print('initializing done') + print("") + return True + + + +#4) + def get_offset(self): + nodeID = ctypes.wintypes.WORD(0) + buf = ctypes.wintypes.DWORD(0) + self.lib.GetObject.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.GetObject.restype = ctypes.wintypes.BOOL + #DC - These are hardcoded values I got from the LabVIEW program -- I don't think any documentation exists on particular object indices + StoredPositionObject = ctypes.wintypes.WORD(8321) + StoredPositionObjectSubindex = ctypes.c_uint8(0) + StoredPositionNbBytesToRead = ctypes.wintypes.DWORD(4) + ObjectData = ctypes.c_void_p() + ObjectDataArray = (ctypes.c_uint32*1)() + ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_int32)) + StoredPositionNbBytesRead = ctypes.pointer(ctypes.wintypes.DWORD(0)) + ret = self.lib.GetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToRead, StoredPositionNbBytesRead, ctypes.byref(buf)) + # Cast the object data to uint32 + CastedObjectData = ctypes.cast(ObjectData, ctypes.POINTER(ctypes.c_int32)) + if ret == 0: + logging.error(__name__ + ' Could not read stored position from Sacher EPOS motor') + print('motor offset value is: %s' % CastedObjectData[0]) + return CastedObjectData[0] + + +#5) + def get_motor_position(self): + nodeID = ctypes.wintypes.WORD(0) + buf = ctypes.wintypes.DWORD(0) + pPosition = ctypes.pointer(ctypes.c_long()) + self.lib.GetPositionIs.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.POINTER(ctypes.c_long), ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.GetPositionIs.restype = ctypes.wintypes.BOOL + ret = self.lib.GetPositionIs(self._keyhandle, nodeID, pPosition, ctypes.byref(buf)) + print('motor position value is: %s' % pPosition.contents.value) + return pPosition.contents.value + +#6) + @Feat() + def wavelength(self): + self._offset = self.get_offset() + self._motor_position = self.get_motor_position() + self._currentwl1 = self._doubleA*(self._offset)**2.0 + self._doubleB*self._offset + self._doubleC + self._currentwl2 = self._doubleA*(self._motor_position)**2.0 + self._doubleB*self._motor_position + self._doubleC + print('Current wavelength according to offset: %.3f nm' % self._currentwl1) + print('Current wavelength according to motor position: %.3f nm' % self._currentwl2) + return self._currentwl1 + + """ + And now we move on to setting things + """ + @wavelength.setter + def wavelength(self, wavelength): + """ + Here's the basic procedure: + 1) Convert the desired target wavelength into a motor position, keeping in mind that we're using the offset as the motor position + x is what the motor position should be + 2) Calculate difference between the target position and the stored offset + 3) Prompt some confirmation before we crash this plane + 4) Then actually move the motor + """ + current_wavelength = self.wavelength + current_offset = self.get_offset() + + nodeID = ctypes.wintypes.WORD(0) + buf = ctypes.wintypes.DWORD(0) + x = (-1.0*self._doubleB + np.sqrt(self._doubleB**2.0 - 4.0*self._doubleA*(self._doubleC - wavelength))) / (2.0*self._doubleA) + wavelength_to_pos = int(round(x)) + diff_wavelength_offset = wavelength_to_pos - int(self._offset) + + print('') + print("The current wavelength, as according to the stored offset, is: %s" % current_wavelength) + print("You're about to set the wavelength to: %s" % wavelength) + print("This means moving the motor by %s steps" % diff_wavelength_offset) + print("Where currently, the stored offset is: %s" % current_offset) + # confirm = str(input("Is this ok? (y/n)")) + # + # if confirm == 'y': + # print('Ok then, setting wavelength...') + # print('') + # else: + # print("ok then, shutting the whole thing down!") + # sys.exit(-1) + + if self._HPM and diff_wavelength_offset < 0: + self.set_target_position(diff_wavelength_offset - 10000, False, True) + self.set_target_position(10000, False, True) + else: + self.set_target_position(diff_wavelength_offset, False, True) + + self.set_new_offset(current_offset + diff_wavelength_offset) + current_offset = self.get_offset() + print("Now the stored offset is: %s" % current_offset) + + return + +#7) + def set_new_offset(self, new_offset): + """ + This is NOT using the function "self.lib.MoveToPosition" + So there's no literal motor movement is going on here + It's just storing a value in some sort of saved memory on the instrument itself + """ + nodeID = ctypes.wintypes.WORD(0) + buf = ctypes.wintypes.DWORD(0) + self.lib.SetObject.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.SetObject.restype = ctypes.wintypes.BOOL + StoredPositionObject = ctypes.wintypes.WORD(8321) + StoredPositionObjectSubindex = ctypes.c_uint8(0) + StoredPositionNbBytesToWrite = ctypes.wintypes.DWORD(4) + ObjectDataArray = (ctypes.c_uint32*1)(new_offset) + ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_uint32)) + StoredPositionNbBytesWritten = ctypes.pointer(ctypes.wintypes.DWORD(0)) + ret = self.lib.SetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToWrite, StoredPositionNbBytesWritten, ctypes.byref(buf)) + if ret == 0: + logging.error(__name__ + ' Could not write stored position from Sacher EPOS motor') + return + +#8) + def set_coeffs(self, a, b, c, min_wl, max_wl): + print('') + print("setting coefficients...") + nodeID = ctypes.wintypes.WORD(0) + buf = ctypes.wintypes.DWORD(0) + self.lib.SetObject.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.wintypes.DWORD, ctypes.POINTER(ctypes.wintypes.DWORD), ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.SetObject.restype = ctypes.wintypes.BOOL + d = (min_wl << 16) + max_wl + StoredPositionObject = ctypes.wintypes.WORD(8204) + + for subidx, coeff in enumerate([a, b, c]): + print(subidx, coeff) + StoredPositionObjectSubindex = ctypes.c_uint8(subidx + 1) + StoredPositionNbBytesToWrite = ctypes.wintypes.DWORD(4) + ObjectDataArray = (ctypes.c_uint32*1)(self._doubletou32(coeff)) + ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_uint32)) + StoredPositionNbBytesWritten = ctypes.pointer(ctypes.wintypes.DWORD(0)) + ret = self.lib.SetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToWrite, StoredPositionNbBytesWritten, ctypes.byref(buf)) + + StoredPositionObjectSubindex = ctypes.c_uint8(4) + StoredPositionNbBytesToWrite = ctypes.wintypes.DWORD(4) + ObjectDataArray = (ctypes.c_uint32*1)(d) + ObjectData = ctypes.cast(ObjectDataArray, ctypes.POINTER(ctypes.c_uint32)) + StoredPositionNbBytesWritten = ctypes.pointer(ctypes.wintypes.DWORD(0)) + ret = self.lib.SetObject(self._keyhandle, nodeID, StoredPositionObject, StoredPositionObjectSubindex, ObjectData, StoredPositionNbBytesToWrite, StoredPositionNbBytesWritten, ctypes.byref(buf)) + + print('Coefficients are %s %s %s' % (self._doubleA, self._doubleB, self._doubleC)) + + if ret == 0: + logging.error(__name__ + ' Could not write stored position from Sacher EPOS motor') + return + + +#9) + def set_target_position(self, target, absolute, immediately): + """ + This is the function that actually moves the motor + Since the motor position this thing reads can't be trusted, we're only doing relative movements and not absolute ones + This means the "absolute" argument should always be set as "false" + + "target" is the target motor position, but be very careful what you want to set this to depending on whether you're doing an absolute or relative movement + + In the "absolute" category, + True starts an absolute movement, False starts a relative movement + Since the absolute position can't be trusted at all, we should always do "False", the relative movement + + In the "immediately" category, + True starts immediately, False waits to end of last positioning + We typically do True for this without any real issues + + The actual money function is "self.lib.MoveToPosition" + """ + + nodeID = ctypes.wintypes.WORD(0) + buf = ctypes.wintypes.DWORD(0) + ret = self.lib.SetEnableState(self._keyhandle,nodeID,ctypes.byref(buf)) + pTarget = ctypes.c_long(target) + pAbsolute = ctypes.wintypes.BOOL(absolute) + pImmediately = ctypes.wintypes.BOOL(immediately) + self.lib.MoveToPosition.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.c_long, ctypes.wintypes.BOOL, ctypes.wintypes.BOOL, ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.MoveToPosition.restype = ctypes.wintypes.BOOL + ret = self.lib.MoveToPosition(self._keyhandle, nodeID, pTarget, pAbsolute, pImmediately, ctypes.byref(buf)) + steps_per_second = 14494.0 # hardcoded, estimated roughly, unused now + nchecks = 0 + while nchecks < 1000: + self._motor_position = self.get_motor_position() + self._offset = self.get_offset() + pMovementState = ctypes.pointer(ctypes.wintypes.BOOL()) + self.lib.GetMovementState.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.POINTER(ctypes.wintypes.BOOL), ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.GetMovementState.restype = ctypes.wintypes.BOOL + ret = self.lib.GetMovementState(self._keyhandle, nodeID, pMovementState, ctypes.byref(buf)) + if pMovementState.contents.value == 1: + break + nchecks = nchecks + 1 + time.sleep(0.01) + ret = self.lib.SetDisableState(self._keyhandle,nodeID,ctypes.byref(buf)) + return ret + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#11) + @staticmethod + def get_bit(byteval, idx): + # def get_bit(self, byteval,idx): + return ((byteval&(1<< idx ))!=0) + """ + We take the two input numbers "byteval" and "idx" and do bitwise operations with them + but it's not clear what the purpose of these bitwise operations are + I also don't know what the @staticmethod decorator is supposed to be doing + """ + +#12) + @staticmethod + def _u32todouble(uinput): + # def _u32todouble(self, uinput): + + # get sign of number + sign = Sacher_EPOS.get_bit(uinput,31) + if sign == False: + mantissa_sign = 1 + elif sign == True: + mantissa_sign = -1 + exp_mask = 0b111111 + #print 'uin u is %d' % uinput + #print 'type uin %s' % type(uinput) + #print 'binary input is %s' % bin(long(uinput)) + # get sign of exponent + if Sacher_EPOS.get_bit(uinput,7) == False: + exp_sign = 1 + elif Sacher_EPOS.get_bit(uinput,7) == True: + exp_sign = -1 + + #print 'exp extract %s' % bin(int(uinput & exp_mask)) + #print 'exp conv %s' % (exp_sign*int(uinput & exp_mask)) + #print 'sign of exponent %s' % self.get_bit(uinput,7) + #print 'binary constant is %s' % bin(int(0b10000000000000000000000000000000)) + mantissa_mask = 0b01111111111111111111111100000000 + # mantissa_mask = 0b0111111111111111111111110000000 + + + #print 'mantissa extract is %s' % bin((uinput & mantissa_mask) >> 8) + mantissa = 1.0/1000000.0*float(mantissa_sign)*float((uinput & mantissa_mask) >> 8) + #print 'mantissa is %.12f' % mantissa + # print(1 if Sacher_EPOS.get_bit(uinput,31) else 0, mantissa, 1 if Sacher_EPOS.get_bit(uinput,7) else 0, uinput & exp_mask) + output = mantissa*2.0**(float(exp_sign)*float(int(uinput & exp_mask))) + #print 'output is %s' % output + return output + """ + ok dc gave some slight explanations here + This function implements the "really weird/non-standard U32 to floating point conversion in the sacher VIs" + It'd be gr8 if I knew what U32's were + unsigned 32 bit something something? + ah whatever + Also I'm seeing mantissas and masks, this is bad + """ + +#13) + @staticmethod + def _doubletou32(dinput): + mantissa_bit = 0 if int(dinput / abs(dinput)) > 0 else 1 + exp_bit = 1 if -1 < dinput < 1 else 0 + + b = np.ceil(np.log10(abs(dinput))) + a = dinput / 10 ** b + if dinput < 0: + a = -a + # print('a:\t{}\tb:\t{}'.format(a, b)) + + d = np.log2(10) * b + d_ = np.ceil(d) + c = a * 2 ** (d - d_) + # print('c:\t{}\td_:{}\toriginal:\t{}'.format(c, d_, c * 2 ** d_)) + + return (int(mantissa_bit) << 31) + (int(c * 1e6) << 8) + (int(exp_bit) << 7) + int(abs(d_)) + + """ + I think this was a new function that we made + + There might be a labview VI that does this correctly + """ + +#14) + def __del__(self): + # execute disconnect + self.close() + return + """ + this might be the only self explanatory one + it disconnects + """ + +#15) + def close(self): + print('closing EPOS motor.') + + self.lib.CloseDevice.argtypes = [ctypes.wintypes.HANDLE, ctypes.POINTER(DWORD)] + self.lib.CloseDevice.restype = ctypes.wintypes.BOOL + buf = ctypes.pointer(DWORD(0)) + ret = ctypes.wintypes.BOOL() + + ret = self.lib.CloseDevice(self._keyhandle, buf) + + #print 'close device returned %s' % buf + + if int(buf.contents.value) >= 0: + self._is_open = False + else: + logging.error(__name__ + ' did not close Sacher EPOS motor correctly.') + return + """ + Apparently this closes the EPOS motor + I don't know what "opening" and "closing" the motor means though + and yeah also these random variables don't make any sense to me + """ + + +#16) + def get_motor_current(self): + nodeID = ctypes.wintypes.WORD(0) + self.lib.GetCurrentIs.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.POINTER(ctypes.c_uint8), ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.GetCurrentIs.restype = ctypes.wintypes.BOOL + + motorCurrent = ctypes.c_uint8(0) + buf = ctypes.wintypes.DWORD(0) + ret = self.lib.GetCurrentIs(self._keyhandle, nodeID, ctypes.byref(motorCurrent), ctypes.byref(buf)) + return motorCurrent.value + + """ + Not sure what this is doing yet + """ + + +#17) + def find_home(self): + nodeID = ctypes.wintypes.WORD(0) + self.lib.FindHome.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.c_uint8, ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.FindHome.restype = ctypes.wintypes.BOOL + + buf = ctypes.wintypes.DWORD(0) + ret = self.lib.FindHome(self._keyhandle, nodeID, ctypes.c_uint8(35), ctypes.byref(buf)) + print('Homing: {}'.format(ret)) + return ret + + """ + Not sure what this is doing yet + """ + + +#18) + def restore(self): + nodeID = ctypes.wintypes.WORD(0) + self.lib.FindHome.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD, ctypes.POINTER(ctypes.wintypes.DWORD)] + self.lib.FindHome.restype = ctypes.wintypes.BOOL + buf = ctypes.wintypes.DWORD(0) + ret = self.lib.Restore(self._keyhandle, nodeID, ctypes.byref(buf)) + print('Restore: {}'.format(ret)) + return ret + + """ + Not sure what this is doing yet + """ + + + +#19) + def fine_tuning_steps(self, steps): + current_motor_pos = self.get_motor_position() + self._offset = self.get_offset() + self.set_target_position(steps, False, True) + new_motor_pos = self.get_motor_position() + #print('New motor position is %s' % new_motor_pos) + #print 'new offset is %s' % (new_motor_pos-current_motor_pos+self._offset) + self.set_new_offset(new_motor_pos-current_motor_pos+self._offset) + + """ + Not sure what this is doing yet + """ + +#20) + def is_open(self): + return self._is_open + +#21) + def clear_fault(self): + nodeID = ctypes.wintypes.WORD(0) + buf = ctypes.wintypes.DWORD(0) + ret = self.lib.ClearFault(self._keyhandle,nodeID,ctypes.byref(buf)) + print('clear fault buf %s, ret %s' % (buf, ret)) + if ret == 0: + errbuf = ctypes.create_string_buffer(64) + self.lib.GetErrorInfo(buf, errbuf, WORD(64)) + raise ValueError(errbuf.value) + """ + Not sure what this is doing yet + """ + + + + +""" +We're done with the Sacher_EPOS() class at this point +""" diff --git a/lantz/drivers/smaract/smaract_motion.py b/lantz/drivers/smaract/smaract_motion.py new file mode 100644 index 0000000..ff85f31 --- /dev/null +++ b/lantz/drivers/smaract/smaract_motion.py @@ -0,0 +1,233 @@ +# -*- coding: utf-8 -*- +""" + lantz.drivers.newport.motion axis + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + General class that implements the commands used for several smaract motion + drivers using the ASCII mode (via serial or serial via USB). + + :copyright: 2018, see AUTHORS for more details. + :license: GPL, see LICENSE for more details. + + Source: Instruction Manual (Newport) + +""" + + +from lantz.feat import Feat +from lantz.action import Action +from lantz.messagebased import MessageBasedDriver +from pyvisa import constants +from lantz import Q_, ureg +from lantz.processors import convert_to +from lantz.drivers.motion import MotionAxisMultiple, MotionControllerMultiAxis, BacklashMixing +import time +import numpy as np + +formats = {'one_param': ''} + +class SCU(MessageBasedDriver, MotionControllerMultiAxis): + """ Driver for SCU controller with multiple axis + + """ + DEFAULTS = { + 'COMMON': {'write_termination': '\n', + 'read_termination': '\n', }, + 'ASRL': { + 'timeout': 100, # ms + 'encoding': 'ascii', + 'data_bits': 8, + 'baud_rate': 9600, + 'parity': constants.Parity.none, + 'stop_bits': constants.StopBits.one, + #'flow_control': constants.VI_ASRL_FLOW_NONE, + 'flow_control': constants.VI_ASRL_FLOW_XON_XOFF, # constants.VI_ASRL_FLOW_NONE, + }, + } + + def initialize(self): + super().initialize() + self.detect_axis() + + def query(self, command, *, send_args=(None, None), recv_args=(None, None)): + return MotionControllerMultiAxis.query(self, ':{}'.format(command), + send_args=send_args, recv_args=recv_args) + + def write(self, command, *args, **kwargs): + return MotionControllerMultiAxis.write(self,':{}'.format(command), + *args, **kwargs) + + @Feat() + def idn(self): + return self.parse_query('I', format='I{:s}') + + @Action() + def detect_axis(self): + """ Find the number of axis available. + + The detection stops as soon as an empty controller is found. + """ + pass + + +class MotionAxis(MotionAxisMultiple, BacklashMixing): + def __del__(self): + self.parent = None + self.num = None + + def query(self, command, *, send_args=(None, None), recv_args=(None, None)): + return self.parent.query('{:d}{}'.format(self.num, command), + send_args=send_args, recv_args=recv_args) + + def write(self, command, *args, **kwargs): + return self.parent.write('{:d}{}'.format(self.num, command), + *args, **kwargs) + + @Feat() + def idn(self): + return self.query('ID?') + + @Action() + def on(self): + """Put axis on""" + self.write('MO') + + @Action() + def off(self): + """Put axis on""" + self.write('MF') + + @Feat(values={True: '1', False: '0'}) + def is_on(self): + """ + :return: True is axis on, else false + """ + return self.query('MO?') + + @Action(units='mm') + def define_home(self, val=0): + """Remap current position to home (0), or to new position + + :param val: new position""" + self.write('DH%f' % val) + + @Action() + def home(self): + """Execute the HOME command""" + self.write('OR') + + @Feat(units='mm') + def position(self): + return self.query('TP?') + + @position.setter + def position(self, pos): + """ + Waits until movement is done if self.wait_until_done = True. + + :param pos: new position + """ + if not self.is_on: + self.log_error('Axis not enabled. Not moving!') + return + + # First do move to extra position if necessary + self._set_position(pos, wait=self.wait_until_done) + + + def __set_position(self, pos): + """ + Move stage to a certain position + :param pos: New position + """ + self.write('PA%f' % (pos)) + self.last_set_position = pos + + @Feat(units='mm/s') + def max_velocity(self): + return float(self.query('VU?')) + + @max_velocity.setter + def max_velocity(self, velocity): + self.write('VU%f' % (velocity)) + + @Feat(units='mm/s**2') + def max_acceleration(self): + return float(self.query('AU?')) + + @max_acceleration.setter + def max_acceleration(self, velocity): + self.write('AU%f' % (velocity)) + + @Feat(units='mm/s') + def velocity(self): + return float(self.query('VA?')) + + @velocity.setter + def velocity(self, velocity): + """ + :param velocity: Set the velocity that the axis should use when moving + :return: + """ + self.write('VA%f' % (velocity)) + + @Feat(units='mm/s**2') + def acceleration(self): + return float(self.query('VA?')) + + @acceleration.setter + def acceleration(self, acceleration): + """ + :param acceleration: Set the acceleration that the axis should use + when starting + :return: + """ + self.write('AC%f' % (acceleration)) + + @Feat(units='mm/s') + def actual_velocity(self): + return float(self.query('TV')) + + @actual_velocity.setter + def actual_velocity(self, val): + raise NotImplementedError + + @Action() + def stop(self): + """Emergency stop""" + self.write('ST') + + @Feat(values={True: '1', False: '0'}) + def motion_done(self): + return self.query('MD?') + + # Not working yet, see https://github.com/hgrecco/lantz/issues/35 + # @Feat(values={Q_('encodercount'): 0, + # Q_('motor step'): 1, + # Q_('millimeter'): 2, + # Q_('micrometer'): 3, + # Q_('inches'): 4, + # Q_('milli-inches'): 5, + # Q_('micro-inches'): 6, + # Q_('degree'): 7, + # Q_('gradian'): 8, + # Q_('radian'): 9, + # Q_('milliradian'): 10, + # Q_('microradian'): 11}) + @Feat() + def units(self): + ret = int(self.query(u'SN?')) + return UNITS[ret] + + @units.setter + def units(self, val): + # No check implemented yet + self.write('%SN%' % (self.num, UNITS.index(val))) + super().units = val + + def _wait_until_done(self): + # wait_time = convert_to('seconds', on_dimensionless='warn')(self.wait_time) + time.sleep(self.wait_time) + while not self.motion_done: + time.sleep(self.wait_time) + return True diff --git a/lantz/feat.py b/lantz/feat.py index d950364..7efd1f5 100644 --- a/lantz/feat.py +++ b/lantz/feat.py @@ -12,6 +12,7 @@ import time import copy +import numpy as np from weakref import WeakKeyDictionary from . import Q_ @@ -65,7 +66,43 @@ def _dset(adict, value, instance=MISSING, key=MISSING): adict[instance][key] = value -class Feat(object): +class FeatActionUpdateModifiersMixing(): + """ + Adds functionality to a class to change its modifiers + """ + def change_units(self, units=MISSING): + """Changes the units of a self""" + return self.change_modifiers( units=units) + + def change_values(self, values=MISSING): + """Changes the values of a self""" + return self.change_modifiers( values=values) + + def change_limits(self, limits=MISSING): + """Changes the values of a self""" + return self.change_modifiers( limits=limits) + + def change_processors(self, processors=MISSING): + """Changes the values of a self""" + return self.change_modifiers( processors=processors) + + def change_precision(self, precision=MISSING): + """Changes the values of a self""" + return self.change_modifiers(precision=precision) + + def change_modifiers(self, **kwargs): + """Updates the modfieres of a self""" + + # Keep self.modifiers and _get_processors in sync + modifiers = self.modifiers[MISSING][MISSING] + modifiers.update(kwargs) + print('Update', dict(modifiers)) + retval = self.rebuild(build_doc=True, modifiers=modifiers, store=True) + + print('After update', self.modifiers[MISSING][MISSING]) + + +class FeatBase(object): """Pimped Python property for interfacing with instruments. Can be used as a decorator. @@ -86,7 +123,14 @@ class Feat(object): If a list/tuple instead of a dict is given, the value is not changed but only tested to belong to the container. :param units: `Quantity` or string that can be interpreted as units. + :param limits: ([start,] stop[, step]) Define range in which value has to be. + Including start and stop. If you provide a value outside the valid + range, Lantz will raise a ValueError. If the steps parameter is set + but you provide a value not compatible with it, it will be silently + rounded. :param procs: Other callables to be applied to input arguments. + :param precision: Only write to an instrument if the set value is more than one + precision away from last known value """ @@ -94,7 +138,7 @@ class Feat(object): def __init__(self, fget=MISSING, fset=None, doc=None, *, values=None, units=None, limits=None, procs=None, - read_once=False): + read_once=False, precision=0): self.fget = fget self.fset = fset self.__doc__ = doc @@ -119,7 +163,8 @@ def __init__(self, fget=MISSING, fset=None, doc=None, *, self.modifiers[MISSING] = {MISSING: {'values': values, 'units': units, 'limits': limits, - 'processors': procs}} + 'processors': procs, + 'precision': precision}} self.get_processors[MISSING] = {MISSING: ()} self.set_processors[MISSING] = {MISSING: ()} @@ -135,6 +180,7 @@ def rebuild(self, instance=MISSING, key=MISSING, build_doc=False, modifiers=None units = modifiers['units'] limits = modifiers['limits'] processors = modifiers['processors'] + precision = modifiers['precision'] get_processors = [] set_processors = [] @@ -155,6 +201,14 @@ def rebuild(self, instance=MISSING, key=MISSING, build_doc=False, modifiers=None get_processors.append(Processor(getp)) if setp is not None: set_processors.append(Processor(setp)) + if precision: + if isinstance(precision, str): + if not units: + raise ValueError('Units have to be set for precision of ' + 'type {}', type(precision)) + precision = ToQuantityProcessor(units)(precision) + if isinstance(precision, Q_): + precision = FromQuantityProcessor(units)(precision) if build_doc: _dochelper(self) @@ -255,9 +309,11 @@ def set(self, instance, value, force=False, key=MISSING): # and timing, caching, logging and error handling with instance._lock: current_value = self.get_cache(instance, key) - - if not force and value == current_value: - instance.log_info('No need to set {} = {} (current={}, force={})', name, value, current_value, force) + modifiers = _dget(self.modifiers, instance, key) + precision = modifiers['precision'] + if not force and (current_value is MISSING or + np.isclose(value, current_value, atol=precision)): + instance.log_info('No need to set {} = {} (current={}, force={}, precision={})', name, value, current_value, force, precision) return instance.log_info('Setting {} = {} (current={}, force={})', name, value, current_value, force) @@ -314,6 +370,10 @@ def set_cache(self, instance, value, key=MISSING): getattr(instance, self.name + '_changed').emit(value, old_value) +class Feat(FeatBase, FeatActionUpdateModifiersMixing): + pass + + class DictFeat(Feat): """Pimped Python property with getitem access for interfacing with instruments. Can be used as a decorator. diff --git a/lantz/processors.py b/lantz/processors.py index 65ee831..c6eb158 100644 --- a/lantz/processors.py +++ b/lantz/processors.py @@ -88,7 +88,7 @@ def _inner(value): elif on_dimensionless == 'warn': msg = 'Assuming units `{1.units}` for {0}'.format(value, units) warnings.warn(msg, DimensionalityWarning) - _LOG.warn(msg) + _LOG.warning(msg) # on_incompatible == 'ignore' return float(value) diff --git a/lantz/testsuite/test_feat.py b/lantz/testsuite/test_feat.py index 752a68b..e0b464d 100644 --- a/lantz/testsuite/test_feat.py +++ b/lantz/testsuite/test_feat.py @@ -261,14 +261,15 @@ def eggs(self_, value): obj.eggs = x obj.eggs = x + 1 - self.assertEqual(hdl.history, ['Created Spam17', + #First item number depends on number of calls of Spam(), hence ignore + self.assertEqual(hdl.history[1:], ['Created Spam17', 'Getting eggs', '(raw) Got 9 for eggs', 'Got 9 for eggs', 'No need to set eggs = 9 (current=9, force=False)', 'Setting eggs = 10 (current=9, force=False)', '(raw) Setting eggs = 10', - 'eggs was set to 10']) + 'eggs was set to 10'][1:]) def test_units(self): diff --git a/requirements-full.txt b/requirements-full.txt index 3e98d8e..4dad18d 100644 --- a/requirements-full.txt +++ b/requirements-full.txt @@ -2,4 +2,5 @@ colorama pyserial pyusb numpy +stringparser -r requirements-doc.txt diff --git a/scripts/lantz-monitor b/scripts/lantz-monitor old mode 100644 new mode 100755 index 40d2aa3..37ef80d --- a/scripts/lantz-monitor +++ b/scripts/lantz-monitor @@ -444,15 +444,20 @@ class LantzMonitor(SocketListener): if record.levelno < self.level: return with self._lock: - queue = record.lantz_driver, record.lantz_name - if queue not in self.queues: - self.queues[queue] = InstrumentState() - self.retitle() - if len(self.queues) == 1: - for formatter in self.formatters: - formatter.current = queue - self.queues[queue].handle(record) - self.handler.handle(record) + try: + queue = record.lantz_driver, record.lantz_name + except AttributeError: + #TODO + pass + else: + if queue not in self.queues: + self.queues[queue] = InstrumentState() + self.retitle() + if len(self.queues) == 1: + for formatter in self.formatters: + formatter.current = queue + self.queues[queue].handle(record) + self.handler.handle(record) if __name__ == '__main__':