def move(comp_name, moves, check_distance=True, to_radians=False): """ move (relatively) the axis of the given component by the specified amount of µm comp_name (str): name of the component moves (dict str -> str): axis -> distance (as text, and in µm for distances) check_distance (bool): if the axis is in meters, check that the move is not too big. to_radians (bool): will convert from degrees to radians if the axis is in radians, otherwise will fail """ # for safety reason, we use µm instead of meters, as it's harder to type a # huge distance component = get_component(comp_name) act_mv = {} # axis -> value for axis_name, str_distance in moves.items(): try: if axis_name not in component.axes: raise ValueError("Actuator %s has no axis '%s'" % (comp_name, axis_name)) ad = component.axes[axis_name] except (TypeError, AttributeError): raise ValueError("Component %s is not an actuator" % comp_name) if ad.unit == "m": try: # Use convert_to_object() to allow typing negative values with e: # -1e-6 => '!!float -1.0e-6'. It's not very nice, but does work. distance = float(convert_to_object(str_distance)) * 1e-6 # µm -> m except ValueError: raise ValueError("Distance '%s' cannot be converted to a number" % str_distance) if check_distance and abs(distance) > MAX_DISTANCE: raise IOError("Distance of %f m is too big (> %f m), use '--big-distance' to allow the move." % (abs(distance), MAX_DISTANCE)) else: distance = convert_to_object(str_distance) if to_radians: if ad.unit == "rad": distance = math.radians(distance) else: raise ValueError("Axis %s is in %s, doesn't support value in degrees" % (axis_name, ad.unit)) act_mv[axis_name] = distance logging.info(u"Will move %s.%s by %s", comp_name, axis_name, units.readable_str(distance, ad.unit, sig=3)) try: m = component.moveRel(act_mv) try: m.result(120) except KeyboardInterrupt: logging.warning("Cancelling relative move of component %s", comp_name) m.cancel() raise except Exception as exc: raise IOError("Failed to move component %s by %s: %s" % (comp_name, act_mv, exc))
def move(comp_name, moves, check_distance=True): """ move (relatively) the axis of the given component by the specified amount of µm comp_name (str): name of the component check_distance (bool): if the axis is in meters, check that the move is not too big. moves (dict str -> str): axis -> distance (as text, and in µm for distances) """ # for safety reason, we use µm instead of meters, as it's harder to type a # huge distance component = get_component(comp_name) act_mv = {} # axis -> value for axis_name, str_distance in moves.items(): try: if axis_name not in component.axes: raise ValueError("Actuator %s has no axis '%s'" % (comp_name, axis_name)) ad = component.axes[axis_name] except (TypeError, AttributeError): raise ValueError("Component %s is not an actuator" % comp_name) if ad.unit == "m": try: # Use convert_to_object() to allow typing negative values with e: # -1e-6 => '!!float -1.0e-6'. It's not very nice, but does work. distance = float(convert_to_object(str_distance)) * 1e-6 # µm -> m except ValueError: raise ValueError("Distance '%s' cannot be converted to a number" % str_distance) if check_distance and abs(distance) > MAX_DISTANCE: raise IOError("Distance of %f m is too big (> %f m), use '--big-distance' to allow the move." % (abs(distance), MAX_DISTANCE)) else: distance = convert_to_object(str_distance) act_mv[axis_name] = distance logging.info(u"Will move %s.%s by %s", comp_name, axis_name, units.readable_str(distance, ad.unit, sig=3)) try: m = component.moveRel(act_mv) try: m.result(120) except KeyboardInterrupt: logging.warning("Cancelling relative move of component %s", comp_name) m.cancel() raise except Exception as exc: raise IOError("Failed to move component %s by %s: %s" % (comp_name, act_mv, exc))
def test_convertToObject_good(self): """ check various inputs and compare to expected output for values that should work """ # example value / input str / expected output tc = [("-1561", -1561), ("0.123", 0.123), ("true", True), ("c: 6,d: 1.3", {"c": 6., "d":1.3}), ("-9, -8", [-9, -8]), (" 9, -8", [9, -8]), ("0, -8, -15.e-3, 6.", [0, -8, -15e-3, 6.0]), ("0.1", 0.1), ("[aa,bb]", ["aa", "bb"]), # TODO: more complicated but nice to support for the user # ("256 x 256 px", (256, 256)), # ("21 x 0.2 m", (21, 0.2)), ("", None), ("{5: }", {5: None}), # Should it fail? ("-1, 63, 12", [-1, 63, 12]), # NotifyingList becomes a list ("9.3, -8", [9.3, -8]), # Note: we don't support SI prefixes ("[aa, c a]", ["aa", "c a"]), ] for str_val, expo in tc: out = convert_to_object(str_val) self.assertEqual(out, expo, "Testing with '%s' -> %s" % (str_val, out))
def update_metadata(comp_name, key_val_str): """ update the metadata of the given component with the given key/value key_val_str (dict str->str): key name -> value as a string """ component = get_component(comp_name) md = {} for key_name, str_val in key_val_str.items(): # Check that the metadata is a valid one. It's a bit tricky as there is no # "official" list. But we look at the ones defined in model.MD_* for n, v in inspect_getmembers(model, lambda m: isinstance(m, str)): if n.startswith("MD_") and v == key_name: key = key_name break else: # fallback to looking for MD_{key_name} try: key = getattr(model, "MD_%s" % key_name) except AttributeError: raise ValueError("Metadata key '%s' is unknown" % key_name) md[key] = convert_to_object(str_val) try: component.updateMetadata(md) except Exception as exc: raise IOError("Failed to update metadata of %s to %s: %s" % (comp_name, md, exc))
def update_metadata(comp_name, key_val_str): """ update the metadata of the given component with the given key/value key_val_str (dict str->str): key name -> value as a string """ component = get_component(comp_name) md = {} for key_name, str_val in key_val_str.items(): # Check that the metadata is a valid one. It's a bit tricky as there is no # "official" list. But we look at the ones defined in model.MD_* for n, v in inspect.getmembers(model, lambda m: isinstance(m, str)): if n.startswith("MD_") and v == key_name: key = key_name break else: # fallback to looking for MD_{key_name} try: key = getattr(model, "MD_%s" % key_name) except AttributeError: raise ValueError("Metadata key '%s' is unknown" % key_name) md[key] = convert_to_object(str_val) try: component.updateMetadata(md) except Exception as exc: raise IOError("Failed to update metadata of %s to %s: %s" % (comp_name, md, exc))
def set_attr(comp_name, attr_val_str): """ set the value of vigilant attribute of the given component. attr_val_str (dict str->str): attribute name -> value as a string """ component = get_component(comp_name) for attr_name, str_val in attr_val_str.items(): try: attr = getattr(component, attr_name) except Exception: raise ValueError("Failed to find attribute '%s' on component '%s'" % (attr_name, comp_name)) if not isinstance(attr, model.VigilantAttributeBase): raise ValueError("'%s' is not a vigilant attribute of component %s" % (attr_name, comp_name)) new_val = convert_to_object(str_val) # Special case for floats, due to rounding error, it's very hard to put the # exact value if it's an enumerated VA. So just pick the closest one in this # case. if isinstance(new_val, float) and ( hasattr(attr, "choices") and isinstance(attr.choices, collections.Iterable)): orig_val = new_val new_val = util.find_closest(new_val, attr.choices) if new_val != orig_val: logging.debug("Adjusting value to %s", new_val) try: attr.value = new_val except Exception as exc: raise IOError("Failed to set %s.%s = '%s': %s" % (comp_name, attr_name, str_val, exc))
def test_convertToObject_bad(self): """ check various inputs and compare to expected output for values that should raise an exception """ # example value / input str tc = [("{5:"), ("[5.3"), # ("5,6]"), # TODO ] for str_val in tc: with self.assertRaises((ValueError, TypeError)): out = convert_to_object(str_val)
def test_convertToObject_bad(self): """ check various inputs and compare to expected output for values that should raise an exception """ # example value / input str tc = [ "{5:", "[5.3", # ("5,6]"), # TODO ] for str_val in tc: with self.assertRaises((ValueError, TypeError)): out = convert_to_object(str_val)
def set_attr(comp_name, attr_val_str): """ set the value of vigilant attribute of the given component. attr_val_str (dict str->str): attribute name -> value as a string """ component = get_component(comp_name) for attr_name, str_val in attr_val_str.items(): try: attr = getattr(component, attr_name) except Exception: raise ValueError( "Failed to find attribute '%s' on component '%s'" % (attr_name, comp_name)) if not isinstance(attr, model.VigilantAttributeBase): raise ValueError( "'%s' is not a vigilant attribute of component %s" % (attr_name, comp_name)) new_val = convert_to_object(str_val) # Special case for floats, due to rounding error, it's very hard to put the # exact value if it's an enumerated VA. So just pick the closest one in this # case. if (isinstance(new_val, float) and hasattr(attr, "choices") and isinstance(attr.choices, collections.Iterable)): orig_val = new_val choices = [ v for v in attr.choices if isinstance(v, numbers.Number) ] new_val = util.find_closest(new_val, choices) if new_val != orig_val: logging.debug("Adjusting value to %s", new_val) # Special case for None being referred to as "null" in YAML, but we should # also accept "None" elif new_val == "None" and not isinstance(attr.value, basestring): new_val = None logging.debug("Adjusting value to %s (null)", new_val) try: attr.value = new_val except Exception as exc: raise IOError("Failed to set %s.%s = '%s': %s" % (comp_name, attr_name, str_val, exc))
def test_convertToObject_good(self): """ check various inputs and compare to expected output for values that should work """ # example value / input str / expected output tc = [ ("-1561", -1561), ("0.123", 0.123), ("true", True), ("c: 6,d: 1.3", { "c": 6., "d": 1.3 }), ("-9, -8", [-9, -8]), (" 9, -8", [9, -8]), ("0, -8, -15.e-3, 6.", [0, -8, -15e-3, 6.0]), ("0.1", 0.1), ("[aa,bb]", ["aa", "bb"]), # TODO: more complicated but nice to support for the user # ("256 x 256 px", (256, 256)), # ("21 x 0.2 m", (21, 0.2)), ("", None), ("{5: }", { 5: None }), # Should it fail? ("-1, 63, 12", [-1, 63, 12]), # NotifyingList becomes a list ("9.3, -8", [9.3, -8]), # Note: we don't support SI prefixes ("[aa, c a]", ["aa", "c a"]), ] for str_val, expo in tc: out = convert_to_object(str_val) self.assertEqual(out, expo, "Testing with '%s' -> %s" % (str_val, out))
def move_abs(comp_name, moves, check_distance=True): """ move (in absolute) the axis of the given component to the specified position comp_name (str): name of the component check_distance (bool): if the axis is in meters, check that the move is not too big. moves (dict str -> str): axis -> position (as text) """ component = get_component(comp_name) act_mv = {} # axis -> value for axis_name, str_position in moves.items(): try: if axis_name not in component.axes: raise ValueError("Actuator %s has no axis '%s'" % (comp_name, axis_name)) ad = component.axes[axis_name] except (TypeError, AttributeError): raise ValueError("Component %s is not an actuator" % comp_name) # Allow the user to indicate the position via the user-friendly choice entry position = None if hasattr(ad, "choices") and isinstance(ad.choices, dict): for key, value in ad.choices.items(): if value == str_position: logging.info("Converting '%s' into %s", str_position, key) position = key # Even if it's a big distance, we don't complain as it's likely # that all choices are safe break if position is None: if ad.unit == "m": try: position = float(convert_to_object(str_position)) except ValueError: raise ValueError("Position '%s' cannot be converted to a number" % str_position) # compare to the current position, to see if the new position sounds reasonable cur_pos = component.position.value[axis_name] if check_distance and abs(cur_pos - position) > MAX_DISTANCE: raise IOError("Distance of %f m is too big (> %f m), use '--big-distance' to allow the move." % (abs(cur_pos - position), MAX_DISTANCE)) else: position = convert_to_object(str_position) # If only a couple of positions are possible, and asking for a float, # avoid the rounding error by looking for the closest possible if (isinstance(position, numbers.Real) and hasattr(ad, "choices") and isinstance(ad.choices, collections.Iterable) and position not in ad.choices): closest = util.find_closest(position, ad.choices) if util.almost_equal(closest, position, rtol=1e-3): logging.debug("Adjusting value %.15g to %.15g", position, closest) position = closest act_mv[axis_name] = position if isinstance(position, numbers.Real): pos_pretty = units.readable_str(position, ad.unit, sig=3) else: pos_pretty = "%s" % (position,) logging.info(u"Will move %s.%s to %s", comp_name, axis_name, pos_pretty) try: m = component.moveAbs(act_mv) try: m.result(120) except KeyboardInterrupt: logging.warning("Cancelling absolute move of component %s", comp_name) m.cancel() raise except Exception as exc: raise IOError("Failed to move component %s to %s: %s" % (comp_name, act_mv, exc))
def move_abs(comp_name, moves, check_distance=True): """ move (in absolute) the axis of the given component to the specified position comp_name (str): name of the component check_distance (bool): if the axis is in meters, check that the move is not too big. moves (dict str -> str): axis -> position (as text) """ component = get_component(comp_name) act_mv = {} # axis -> value for axis_name, str_position in moves.items(): try: if axis_name not in component.axes: raise ValueError("Actuator %s has no axis '%s'" % (comp_name, axis_name)) ad = component.axes[axis_name] except (TypeError, AttributeError): raise ValueError("Component %s is not an actuator" % comp_name) # Allow the user to indicate the position via the user-friendly choice entry position = None if (hasattr(ad, "choices") and isinstance(ad.choices, dict)): for key, value in ad.choices.items(): if value == str_position: logging.info("Converting '%s' into %s", str_position, key) position = key # Even if it's a big distance, we don't complain as it's likely # that all choices are safe break if position is None: if ad.unit == "m": try: position = float(str_position) except ValueError: raise ValueError("Position '%s' cannot be converted to a number" % str_position) # compare to the current position, to see if the new position sounds reasonable cur_pos = component.position.value[axis_name] if check_distance and abs(cur_pos - position) > MAX_DISTANCE: raise IOError("Distance of %f m is too big (> %f m), use '--big-distance' to allow the move." % (abs(cur_pos - position), MAX_DISTANCE)) else: position = convert_to_object(str_position) # If only a couple of positions are possible, and asking for a float, # avoid the rounding error by looking for the closest possible if (isinstance(position, numbers.Real) and hasattr(ad, "choices") and isinstance(ad.choices, collections.Iterable) and position not in ad.choices): closest = util.find_closest(position, ad.choices) if util.almost_equal(closest, position, rtol=1e-3): logging.debug("Adjusting value %.15g to %.15g", position, closest) position = closest act_mv[axis_name] = position logging.info(u"Will move %s.%s to %s", comp_name, axis_name, units.readable_str(position, ad.unit, sig=3)) try: m = component.moveAbs(act_mv) try: m.result(120) except KeyboardInterrupt: logging.warning("Cancelling absolute move of component %s", comp_name) m.cancel() raise except Exception as exc: raise IOError("Failed to move component %s to %s: %s" % (comp_name, act_mv, exc))