#!/usr/bin/env python
"""
Provides support for the Newport Pico Motor Controller 8742
Note that the class is currently only tested with one controller connected,
however, a main controller / secondary controller setup has also been
implemented already. Commands are as described in the Picomotor manual.
If a connection via TCP/IP is opened, the standard port that these devices
listen to is 23.
If you have only one controller connected, everything should work out of
the box. Please only use axiss 0 through 3.
If you have multiple controllers connected (up to 31), you need to set the
addresses of each controller. This can be done with this this class. See,
e.g., routines for `controller_address`, `scan_controller`, and `scan`.
Also make sure that you set `multiple_controllers` to `True`. This is
used for internal handling of the class only and does not communicate with
the instruments.
If you run with multiple controllers, the axiss are as following:
Ch 0 - 3 -> Motors 1 - 4 on controller with address 1
Ch 4 - 7 -> Motors 1 - 4 on controller with address 2
Ch i - i+4 -> Motors 1 - 4 on controller with address i / 4 + 1 (with i%4 = 0)
All network commands only work with the main controller (this should make
sense).
If in multiple controller mode, you can always send controller specific
commands by sending them to one individual axis of that controller.
Any axis works!
"""
# IMPORTS #
from enum import IntEnum
from instruments.abstract_instruments import Instrument
from instruments.units import ureg as u
from instruments.util_fns import assume_units, ProxyList
# pylint: disable=too-many-lines
[docs]
class PicoMotorController8742(Instrument):
"""Newport Picomotor Controller 8742 Communications Class
Use this class to communicate with the picomotor controller 8742.
Single-controller and multi-controller setup can be used.
Device can be talked to via TCP/IP or over USB.
FixMe: InstrumentKit currently does not communicate correctly via USB!
Example for TCP/IP controller in single controller mode:
>>> import instruments as ik
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> motor1 = inst.axis[0]
>>> motor1.move_relative = 100
Example for communications via USB:
>>> import instruments as ik
>>> pid = 0x4000
>>> vid = 0x104d
>>> ik.newport.PicoMotorController8742.open_usb(pid=pid, vid=vid)
>>> motor3 = inst.axis[2]
>>> motor3.move_absolute = -200
Example for multicontrollers with controller addresses 1 and 2:
>>> import instruments as ik
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.multiple_controllers = True
>>> contr1mot1 = inst.axis[0]
>>> contr2mot1 = inst.axis[4]
>>> contr1mot1.move_absolute = 200
>>> contr2mot1.move_relative = -212
"""
def __init__(self, filelike):
"""Initialize the PicoMotorController class."""
super().__init__(filelike)
# terminator
self.terminator = "\r\n"
# setup
self._multiple_controllers = False
# INNER CLASSES #
[docs]
class Axis:
"""PicoMotorController8742 Axis class for individual motors."""
def __init__(self, parent, idx):
"""Initialize the axis with the parent and the number.
:raises IndexError: Axis accessed looks like a main / secondary
setup, but the flag for `multiple_controllers` is not set
appropriately. See introduction.
"""
if not isinstance(parent, PicoMotorController8742):
raise TypeError("Don't do that.")
if idx > 3 and not parent.multiple_controllers:
raise IndexError(
"You requested an axis that is only "
"available in multi controller mode, "
"however, have not enabled it. See "
"`multi_controllers` routine."
)
# set controller
self._parent = parent
self._idx = idx % 4 + 1
# set _address:
if self._parent.multiple_controllers:
self._address = f"{idx // 4 + 1}>"
else:
self._address = ""
# ENUMS #
[docs]
class MotorType(IntEnum):
"""IntEnum Class containing valid MotorTypes
Use this enum to set the motor type. You can select that no or an
unkown motor are connected. See also `motor_check` command to set
these values per controller automatically.
"""
none = 0
unknown = 1
tiny = 2
standard = 3
# PROPERTIES #
@property
def acceleration(self):
"""Get / set acceleration of axis in steps / sec^2.
Valid values are between 1 and 200,000 (steps) 1 / sec^2 with the
default as 100,000 (steps) 1 / sec^2. If quantity is not unitful,
it is assumed that 1 / sec^2 is chosen.
:return: Acceleration in 1 / sec^2
:rtype: u.Quantity(int)
:raises ValueError: Limit is out of bound.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> ax = inst.axis[0]
>>> ax.acceleration = u.Quantity(500, 1/u.s**-2)
"""
return assume_units(int(self.query("AC?")), u.s**-2)
@acceleration.setter
def acceleration(self, value):
value = int(assume_units(value, u.s**-2).to(u.s**-2).magnitude)
if not 1 <= value <= 200000:
raise ValueError(
f"Acceleration must be between 1 and "
f"200,000 s^-2 but is {value}."
)
self.sendcmd(f"AC{value}")
@property
def home_position(self):
"""Get / set home position
The home position of the device is used, e.g., when moving
to a specific position instead of a relative move. Valid values
are between -2147483648 and 2147483647.
:return: Home position.
:rtype: int
:raises ValueError: Set value is out of range.
Example:
>>> import instruments as ik
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> ax = inst.axis[0]
>>> ax.home_position = 444
"""
return int(self.query("DH?"))
@home_position.setter
def home_position(self, value):
if not -2147483648 <= value <= 2147483647:
raise ValueError(
f"Home position must be between -2147483648 "
f"and 2147483647, but is {value}."
)
self.sendcmd(f"DH{int(value)}")
@property
def is_stopped(self):
"""Get if an axis is stopped (not moving).
:return: Is the axis stopped?
:rtype: bool
Example:
>>> import instruments as ik
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> ax = inst.axis[0]
>>> ax.is_stopped
True
"""
return bool(int(self.query("MD?")))
@property
def motor_type(self):
"""Set / get the type of motor connected to the axis.
Use a `MotorType` IntEnum to set this motor type.
:return: Motor type set.
:rtype: MotorType
:raises TypeError: Set motor type is not of type `MotorType`.
Example:
>>> import instruments as ik
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> ax = inst.axis[0]
>>> ax.motor_type = ax.MotorType.tiny
"""
retval = int(self.query("QM?"))
return self.MotorType(retval)
@motor_type.setter
def motor_type(self, value):
if not isinstance(value, self.MotorType):
raise TypeError(
f"Set motor type must be of type `MotorType` "
f"but is of type {type(value)}."
)
self.sendcmd(f"QM{value.value}")
@property
def move_absolute(self):
"""Get / set the absolute target position of a motor.
Set with absolute position in steps. Valid values between
-2147483648 and +2147483647.
See also: `home_position`.
:return: Absolute motion target position.
:rtype: int
:raises ValueError: Requested position out of range.
Example:
>>> import instruments as ik
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> ax = inst.axis[0]
>>> ax.move_absolute = 100
"""
return int(self.query("PA?"))
@move_absolute.setter
def move_absolute(self, value):
if not -2147483648 <= value <= 2147483647:
raise ValueError(
f"Set position must be between -2147483648 "
f"and 2147483647, but is {value}."
)
self.sendcmd(f"PA{int(value)}")
@property
def move_relative(self):
"""Get / set the relative target position of a motor.
Set with relative motion in steps. Valid values between
-2147483648 and +2147483647.
See also: `home_position`.
:return: Relative motion target position.
:rtype: int
:raises ValueError: Requested motion out of range.
Example:
>>> import instruments as ik
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> ax = inst.axis[0]
>>> ax.move_relative = 100
"""
return int(self.query("PR?"))
@move_relative.setter
def move_relative(self, value):
if not -2147483648 <= value <= 2147483647:
raise ValueError(
f"Set motion must be between -2147483648 "
f"and 2147483647, but is {value}."
)
self.sendcmd(f"PR{int(value)}")
@property
def position(self):
"""Queries current, actual position of motor.
Positions are with respect to the home position.
:return: Current position in steps.
:rtype: int
Example:
>>> import instruments as ik
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> ax = inst.axis[0]
>>> ax.position
123
"""
return int(self.query("TP?"))
@property
def velocity(self):
"""Get / set velocty of the connected motor (unitful).
Velocity is given in (steps) per second (1/s).
If a `MotorType.tiny` motor is connected, the maximum velocity
allowed is 1750 /s, otherwise 2000 /s.
If no units are given, 1/s are assumed.
:return: Velocity in 1/s
:rtype: u.Quantity(int)
:raises ValueError: Set value is out of the allowed range.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> ax = inst.axis[0]
>>> ax.velocity = u.Quantity(500, 1/u.s)
"""
retval = int(self.query("VA?"))
return u.Quantity(retval, 1 / u.s)
@velocity.setter
def velocity(self, value):
if self.motor_type == self.MotorType.tiny:
max_velocity = 1750
else:
max_velocity = 2000
value = int(assume_units(value, 1 / u.s).to(1 / u.s).magnitude)
if not 0 < value <= max_velocity:
raise ValueError(
f"The maximum allowed velocity for the set "
f"motor is {max_velocity}. The requested "
f"velocity of {value} is out of range."
)
self.sendcmd(f"VA{value}")
# METHODS #
[docs]
def move_indefinite(self, direction):
"""Move the motor indefinitely in the specific direction.
To stop motion, issue `stop_motion` or `abort_motion` command.
Direction is defined as a string of either "+" or "-".
:param direction: Direction in which to move the motor, "+" or "-"
:type direction: str
Example:
>>> from time import sleep
>>> import instruments as ik
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> ax = inst.axis[0]
>>> ax.move_indefinite("+")
>>> sleep(1) # wait a second
>>> ax.stop()
"""
if direction in ["+", "-"]:
self.sendcmd(f"MV{direction}")
[docs]
def stop(self):
"""Stops the specific axis if in motion.
Example:
>>> from time import sleep
>>> import instruments as ik
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> ax = inst.axis[0]
>>> ax.move_indefinite("+")
>>> sleep(1) # wait a second
>>> ax.stop()
"""
self.sendcmd("ST")
# CONTROLLER SPECIFIC PROPERTIES #
@property
def controller_address(self):
"""Get / set the controller address.
Valid address values are between 1 and 31. For setting up multiple
instruments, see `multiple_controllers`.
:return: Address of this device if secondary, otherwise `None`
:rtype: int
"""
retval = int(self.query("SA?", axs=False))
return retval
@controller_address.setter
def controller_address(self, newval):
self.sendcmd(f"SA{int(newval)}", axs=False)
@property
def controller_configuration(self):
"""Get / set configuration of some of the controller’s features.
Configuration is given as a bit mask. If changed, please save
the settings afterwards if you would like to do so. See
`save_settings`.
The bitmask to be set can be either given as a number, or as a
string of the mask itself. The following values are equivalent:
3, 0b11, "11"
Bit 0:
Value 0: Perform auto motor detection. Check and set motor
type automatically when commanded to move.
Value 1: Do not perform auto motor detection on move.
Bit 1:
Value 0: Do not scan for motors connected to controllers upon
reboot (Performs ‘MC’ command upon power-up, reset or
reboot).
Value 1: Scan for motors connected to controller upon power-up
or reset.
:return: Bitmask of the controller configuration.
:rtype: str, binary configuration
"""
return self.query("ZZ?", axs=False)
@controller_configuration.setter
def controller_configuration(self, value):
if isinstance(value, str):
self.sendcmd(f"ZZ{value}", axs=False)
else:
self.sendcmd(f"ZZ{str(bin(value))[2:]}", axs=False)
@property
def error_code(self):
"""Get error code only.
Error code0 means no error detected.
:return: Error code.
:rtype: int
"""
return int(self.query("TE?", axs=False))
@property
def error_code_and_message(self):
"""Get error code and message.
:return: Error code, error message
:rtype: int, str
"""
retval = self.query("TB?", axs=False)
err_code, err_msg = retval.split(",")
err_code = int(err_code)
err_msg = err_msg.strip()
return err_code, err_msg
@property
def firmware_version(self):
"""Get the controller firmware version."""
return self.query("VE?", axs=False)
@property
def name(self):
"""Get the name of the controller."""
return self.query("*IDN?", axs=False)
# CONTROLLER SPECIFIC METHODS #
[docs]
def abort_motion(self):
"""Instantaneously stops any motion in progress."""
self.sendcmd("AB", axs=False)
[docs]
def motor_check(self):
"""Check what motors are connected and set parameters.
Use the save command `save_settings` if you want to save the
configuration to the non-volatile memory.
"""
self.sendcmd("MC", axs=False)
[docs]
def purge(self):
"""Purge the non-volatile memory of the controller.
Perform a hard reset and reset all the saved variables. The
following variables are reset to factory settings:
1. Hostname
2. IP Mode
3. IP Address
4. Subnet mask address
5. Gateway address
6. Configuration register
7. Motor type
8. Desired Velocity
9. Desired Acceleration
"""
self.sendcmd("XX", axs=False)
[docs]
def recall_parameters(self, value=0):
"""Recall parameter set.
This command restores the controller working parameters from values
saved in its non-volatile memory. It is useful when, for example,
the user has been exploring and changing parameters (e.g., velocity)
but then chooses to reload from previously stored, qualified
settings. Note that “*RCL 0” command just restores the working
parameters to factory default settings. It does not change the
settings saved in EEPROM.
:param value: 0 -> Recall factory default,
1 -> Recall last saved settings
:type int:
"""
self.sendcmd(f"*RCL{1 if value else 0}", axs=False)
[docs]
def reset(self):
"""Reset the controller.
Perform a soft reset. Saved variables are not deleted! For a
hard reset, see the `purge` command.
..note:: It might take up to 30 seconds to re-establish
communications via TCP/IP
"""
self.sendcmd("*RST", axs=False)
[docs]
def save_settings(self):
"""Save user settings.
This command saves the controller settings in its non-volatile memory.
The controller restores or reloads these settings to working registers
automatically after system reset or it reboots. The purge
command is used to clear non-volatile memory and restore to factory
settings. Note that the SM saves parameters for all motors.
Saves the following variables:
1. Controller address
2. Hostname
3. IP Mode
4. IP Address
5. Subnet mask address
6. Gateway address
7. Configuration register
8. Motor type
9. Desired Velocity
10. Desired Acceleration
"""
self.sendcmd("SM", axs=False)
# SEND AND QUERY #
[docs]
def sendcmd(self, cmd, axs=True):
"""Send a command to an axis object.
:param cmd: Command
:type cmd: str
:param axs: Send axis address along? Not used for controller
commands. Defaults to `True`
:type axs: bool
"""
if axs:
command = f"{self._address}{self._idx}{cmd}"
else:
command = f"{self._address}{cmd}"
self._parent.sendcmd(command)
[docs]
def query(self, cmd, size=-1, axs=True):
"""Query for an axis object.
:param cmd: Command
:type cmd: str
:param size: bytes to read, defaults to "until terminator" (-1)
:type size: int
:param axs: Send axis address along? Not used for controller
commands. Defaults to `True`
:type axs: bool
:raises IOError: The wrong axis answered.
"""
if axs:
command = f"{self._address}{self._idx}{cmd}"
else:
command = f"{self._address}{cmd}"
retval = self._parent.query(command, size=size)
if retval[: len(self._address)] != self._address:
raise OSError(
f"Expected to hear back from secondary "
f"controller {self._address}, instead "
f"controller {retval[:len(self._address)]} "
f"answered."
)
return retval[len(self._address) :]
@property
def axis(self):
"""Return an axis object.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> ax = inst.axis[0]
"""
return ProxyList(self, self.Axis, range(31 * 4))
@property
def controller_address(self):
"""Get / set the controller address.
Valid address values are between 1 and 31. For setting up multiple
instruments, see `multiple_controllers`.
:return: Address of this device if secondary, otherwise `None`
:rtype: int
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.controller_address = 13
"""
return self.axis[0].controller_address
@controller_address.setter
def controller_address(self, newval):
self.axis[0].controller_address = newval
@property
def controller_configuration(self):
"""Get / set configuration of some of the controller’s features.
Configuration is given as a bit mask. If changed, please save
the settings afterwards if you would like to do so. See
`save_settings`.
Bit 0:
Value 0: Perform auto motor detection. Check and set motor
type automatically when commanded to move.
Value 1: Do not perform auto motor detection on move.
Bit 1:
Value 0: Do not scan for motors connected to controllers upon
reboot (Performs ‘MC’ command upon power-up, reset or
reboot).
Value 1: Scan for motors connected to controller upon power-up
or reset.
:return: Bitmask of the controller configuration.
:rtype: str
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.controller_configuration = "11"
"""
return self.axis[0].controller_configuration
@controller_configuration.setter
def controller_configuration(self, value):
self.axis[0].controller_configuration = value
@property
def dhcp_mode(self):
"""Get / set if device is in DHCP mode.
If not in DHCP mode, a static IP address, gateway, and netmask
must be set.
:return: Status if DHCP mode is enabled
:rtype: `bool`
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.dhcp_mode = True
"""
return bool(self.query("IPMODE?"))
@dhcp_mode.setter
def dhcp_mode(self, newval):
nn = 1 if newval else 0
self.sendcmd(f"IPMODE{nn}")
@property
def error_code(self):
"""Get error code only.
Error code0 means no error detected.
:return: Error code.
:rtype: int
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.error_code
0
"""
return self.axis[0].error_code
@property
def error_code_and_message(self):
"""Get error code and message.
:return: Error code, error message
:rtype: int, str
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.error_code
(0, 'NO ERROR DETECTED')
"""
return self.axis[0].error_code_and_message
@property
def firmware_version(self):
"""Get the controller firmware version.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.firmware_version
'8742 Version 2.2 08/01/13'
"""
return self.axis[0].firmware_version
@property
def gateway(self):
"""Get / set the gateway of the instrument.
:return: Gateway address
:rtype: str
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.gateway = "192.168.1.1"
"""
return self.query("GATEWAY?")
@gateway.setter
def gateway(self, value):
self.sendcmd(f"GATEWAY {value}")
@property
def hostname(self):
"""Get / set the hostname of the instrument.
:return: Hostname
:rtype: `str`
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.hostname = "asdf"
"""
return self.query("HOSTNAME?")
@hostname.setter
def hostname(self, value):
self.sendcmd(f"HOSTNAME {value}")
@property
def ip_address(self):
"""Get / set the IP address of the instrument.
:return: IP address
:rtype: `str`
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.ip_address = "192.168.1.2"
"""
return self.query("IPADDR?")
@ip_address.setter
def ip_address(self, value):
self.sendcmd(f"IPADDR {value}")
@property
def mac_address(self):
"""Get the MAC address of the instrument.
:return: MAC address
:rtype: `str`
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.mac_address
'5827809, 8087'
"""
return self.query("MACADDR?")
@property
def multiple_controllers(self):
"""Get / set if multiple controllers are used.
By default, this is initialized as `False`. Set to `True` if you
have a main controller / secondary controller via RS-485 network
set up.
Instrument commands will always be sent to main controller.
Axis specific commands will be set to the axis chosen, see
`axis` description.
:return: Status if multiple controllers are activated
:rtype: bool
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.multiple_controllers = True
"""
return self._multiple_controllers
@multiple_controllers.setter
def multiple_controllers(self, newval):
self._multiple_controllers = True if newval else False
@property
def name(self):
"""Get the name of the controller.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.name
'New_Focus 8742 v2.2 08/01/13 13991'
"""
return self.axis[0].name
@property
def netmask(self):
"""Get / set the Netmask of the instrument.
:return: Netmask
:rtype: `str`
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.netmask = "255.255.255.0"
"""
return self.query("NETMASK?")
@netmask.setter
def netmask(self, value):
self.sendcmd(f"NETMASK {value}")
@property
def scan_controllers(self):
"""RS-485 controller address map query of all controllers.
32 bit string that represents the following:
Bit: Value: (True: 1, False: 0)
0 Address conflict?
1: Controller with address 1 exists?
...
31: Controller with address 31 exists
Bits 1—31 are one-to-one mapped to controller addresses 1—31. The
bit value is set to 1 only when there are no conflicts with that
address. For example, if the master controller determines that there
are unique controllers at addresses 1,2, and 7 and more than one
controller at address 23, this query will return 135. The binary
representation of 135 is 10000111. Bit #0 = 1 implies that the scan
found at lease one address conflict during last scan. Bit #1,2, 7 = 1
implies that the scan found controllers with addresses 1,2, and 7
that do not conflict with any other controller.
:return: Binary representation of controller configuration bitmask.
:rtype: str
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.scan_controllers
"10000111"
"""
return self.query("SC?")
@property
def scan_done(self):
"""Queries if a controller scan is done or not.
:return: Controller scan done?
:rtype: bool
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.scan_done
True
"""
return bool(int(self.query("SD?")))
# METHODS #
[docs]
def abort_motion(self):
"""Instantaneously stop any motion in progress.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.abort_motion()
"""
self.axis[0].abort_motion()
[docs]
def motor_check(self):
"""Check what motors are connected and set parameters.
Use the save command `save_settings` if you want to save the
configuration to the non-volatile memory.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.motor_check()
"""
self.axis[0].motor_check()
[docs]
def scan(self, value=2):
"""Initialize and set controller addresses automatically.
Scans the RS-485 network for connected controllers and set the
addresses automatically. Three possible scan modes can be
selected:
Mode 0:
Primary controller scans the network but does not resolve
any address conflicts.
Mode 1:
Primary controller scans the network and resolves address
conflicts, if any. This option preserves the non-conflicting
addresses and reassigns the conflicting addresses starting
with the lowest available address.
Mode 2 (default):
Primary controller reassigns the addresses of all
controllers on the network in a sequential order starting
with master controller set to address 1.
See also: `scan_controllers` property.
:param value: Scan mode.
:type: int
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.scan(2)
"""
self.sendcmd(f"SC{value}")
[docs]
def purge(self):
"""Purge the non-volatile memory of the controller.
Perform a hard reset and reset all the saved variables. The
following variables are reset to factory settings:
1. Hostname
2. IP Mode
3. IP Address
4. Subnet mask address
5. Gateway address
6. Configuration register
7. Motor type
8. Desired Velocity
9. Desired Acceleration
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.purge()
"""
self.axis[0].purge()
[docs]
def recall_parameters(self, value=0):
"""Recall parameter set.
This command restores the controller working parameters from values
saved in its non-volatile memory. It is useful when, for example,
the user has been exploring and changing parameters (e.g., velocity)
but then chooses to reload from previously stored, qualified
settings. Note that “*RCL 0” command just restores the working
parameters to factory default settings. It does not change the
settings saved in EEPROM.
:param value: 0 -> Recall factory default,
1 -> Recall last saved settings
:type value: int
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.recall_parameters(1)
"""
self.axis[0].recall_parameters(value)
[docs]
def reset(self):
"""Reset the controller.
Perform a soft reset. Saved variables are not deleted! For a
hard reset, see the `purge` command.
..note:: It might take up to 30 seconds to re-establish
communications via TCP/IP
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.reset()
"""
self.axis[0].reset()
[docs]
def save_settings(self):
"""Save user settings.
This command saves the controller settings in its non-volatile memory.
The controller restores or reloads these settings to working registers
automatically after system reset or it reboots. The purge
command is used to clear non-volatile memory and restore to factory
settings. Note that the SM saves parameters for all motors.
Saves the following variables:
1. Controller address
2. Hostname
3. IP Mode
4. IP Address
5. Subnet mask address
6. Gateway address
7. Configuration register
8. Motor type
9. Desired Velocity
10. Desired Acceleration
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> ip = "192.168.1.2"
>>> port = 23 # this is the default port
>>> inst = ik.newport.PicoMotorController8742.open_tcpip(ip, port)
>>> inst.save_settings()
"""
self.axis[0].save_settings()
# QUERY #
[docs]
def query(self, cmd, size=-1):
"""Query's the device and returns ASCII string.
Must be queried as a raw string with terminator line ending. This is
currently not implemented in instrument and therefore must be called
directly from file.
Sometimes, the instrument sends an undecodable 6 byte header along
(usually for the first query). We'll catch it with a try statement.
The 6 byte header was also remarked in this matlab script:
https://github.com/cnanders/matlab-newfocus-model-8742
"""
self.sendcmd(cmd)
retval = self.read_raw(size=size)
try:
retval = retval.decode("utf-8")
except UnicodeDecodeError:
retval = retval[6:].decode("utf-8")
return retval