devices

Every command in the manual, split by what it applies to:

  • All devices (ElliptecDevice): get_info, refresh_calibration, get_status, save_user_data, change_address, isolate_minutes, group_address, skip_frequency_search, get_motor_info(1|2), set_forward_period/set_forward_frequency, set_backward_period/set_backward_frequency, search_frequency, scan_current_curve, get_position/get_position_pulses, forward/forward_pulses, backward/backward_pulses, get_button_status (non-blocking poll for spontaneous BS/BO messages).

  • Rotary / linear / iris stages (MotionMixin — not multi-position sliders): home/home_pulses, move_absolute/move_absolute_pulses, move_relative/move_relative_pulses, get_home_offset/set_home_offset (+ _pulses variants), get_jog_step_size/set_jog_step_size (+ _pulses variants), get_velocity/set_velocity, stop (see The stop() command).

  • ELL15 iris (AutoHomingMixin): set_auto_homing/set_auto_homing_pulses.

  • ELL14/15/16/17/18/20/21/22 (OptimizeCleanMixin): optimize_motors, clean_mechanics (not on ELL15), stop.

  • ELL22 (ZeroPositionMixin, ResetFactoryMixin): set_zero_position, get_zero_position_offset/get_zero_position_offset_pulses, reset_factory_default.

Capability gating (e.g. no f1/b1 tuning or button messages on ELL16/ELL21/ELL22; no second motor on ELL6) follows the individual “THIS MESSAGE DOES NOT APPLY TO…” notes attached to each command in the manual, since the summary applicability tables at the end of the document are inconsistent with those notes and with each other. Calling an unsupported method raises ElliptecUnsupportedError instead of sending a doomed command to the device.

Base class and capability mixins

Base device class and capability mixins for ELLx modules.

ElliptecDevice implements every command that is common to all ELLx modules (identification, status, saving parameters, addressing, per-motor tuning, isolation). Commands that only some device families support are implemented as mixins (MotionMixin, AutoHomingMixin, OptimizeCleanMixin, ZeroPositionMixin, ResetFactoryMixin) and are composed onto the concrete model classes in tl_elliptec.devices.models according to what section 3 (“applicability”) of each command in the manual states.

class tl_elliptec.devices.base.DeviceInfo(address, ell_type, serial_number, year, firmware_release, hardware_release, is_imperial, travel, pulses_per_unit)[source]

Bases: object

Parsed reply to HOSTREQ_INFORMATION “in” / DEVGET_INFORMATION “IN”.

Parameters:
  • address (str)

  • ell_type (int)

  • serial_number (str)

  • year (str)

  • firmware_release (str)

  • hardware_release (int)

  • is_imperial (bool)

  • travel (int)

  • pulses_per_unit (int)

address: str

Address the device reported from.

ell_type: int

Decimal model number, e.g. 14 for an ELL14 (decoded from the wire’s hex-ASCII field – see from_reply()).

serial_number: str

Device serial number, as an 8-character hex-ASCII string.

year: str

Year of manufacture, as a 4-character string.

firmware_release: str

Firmware release, as a 2-character string.

hardware_release: int

Hardware release number (7 bits, the thread-type bit already split out into is_imperial).

is_imperial: bool

True if the stage uses an imperial-thread lead screw, False if metric.

travel: int

Full travel of the stage, in mm or degrees per the model table (not corrected for any per-model quirks).

pulses_per_unit: int

Raw “PULSES/M.U.” field as reported by the device. On rotary stages this is empirically pulses-per-revolution, not pulses-per-degree – see ElliptecDevice.PULSES_FIELD_IS_PER_REVOLUTION for the correction applied when computing ElliptecDevice.pulses_per_unit.

classmethod from_reply(reply)[source]

Parse an “IN” reply into a DeviceInfo.

Parameters:

reply (Reply) – The device’s reply to a HOSTREQ_INFORMATION “in” request.

Returns:

The parsed device information.

Return type:

DeviceInfo

class tl_elliptec.devices.base.MotorInfo(loop_on, motor_on, current_amps, forward_period, backward_period)[source]

Bases: object

Parsed reply to HOSTREQ_MOTORxINFO “i1”/”i2”.

Parameters:
  • loop_on (bool)

  • motor_on (bool)

  • current_amps (float)

  • forward_period (int)

  • backward_period (int)

loop_on: bool

Whether the motor’s control loop is active.

motor_on: bool

Whether the motor is currently energized.

current_amps: float

Last measured motor current, in amps.

forward_period: int

Raw forward resonant “period” value (see period_for_frequency() to convert to/from Hz).

backward_period: int

Raw backward resonant “period” value.

property forward_frequency_hz: float | None

Forward resonant frequency in Hz, or None if undefined (period is 0 or 0xFFFF).

property backward_frequency_hz: float | None

Backward resonant frequency in Hz, or None if undefined (period is 0 or 0xFFFF).

classmethod from_reply(reply)[source]

Parse an “I1”/”I2” reply into a MotorInfo.

Parameters:

reply (Reply) – The device’s reply to a HOSTREQ_MOTORxINFO “i1”/”i2” request.

Returns:

The parsed motor information.

Return type:

MotorInfo

class tl_elliptec.devices.base.CurrentCurveSample(period, current_amps)[source]

Bases: object

One (frequency, current) sample from ElliptecDevice.scan_current_curve().

Parameters:
period: int

Raw resonant “period” value at this sample (see frequency_hz to convert to Hz).

current_amps: float

Measured motor current at this frequency, in amps.

property frequency_hz: float

This sample’s frequency, in Hz (inf if period is 0).

tl_elliptec.devices.base.period_for_frequency(frequency_hz)[source]

Convert a target resonant frequency to the protocol’s “period” units.

Parameters:

frequency_hz (float) – Target frequency, in Hz.

Returns:

The equivalent raw “period” value, as used by ElliptecDevice.set_forward_period()/set_backward_period.

Return type:

int

class tl_elliptec.devices.base.ElliptecDevice(port_or_bus, address='0', timeout=None, **bus_kwargs)[source]

Bases: object

Commands common to every ELLx module.

Can be constructed two ways:

  • Point-to-point (one controller wired straight to one device):

    stage = ELL14("COM5")               # owns and opens the serial port itself
    
  • Shared multidrop bus (a hub with several addressed devices):

    bus = ElliptecBus("COM5")
    stage = ELL14(bus, address="2")     # bus is shared, not owned/closed by the device
    slider = ELL9(bus, address="3")
    
Parameters:
has_motor2: bool = True

Overridden by subclasses. Some families lack a second motor (ELL6), and ELL16/ELL21/ELL22 do not support tunable forward/backward periods or the hardware-button spontaneous status messages.

supports_motor_tuning: bool = True
supports_button_messages: bool = True
DEFAULT_PULSES_PER_UNIT: float = 1.0

Fallback encoder-pulses-per-unit (per mm or per degree) used by unit-based position methods (move_absolute, get_position, …) when live calibration from get_info() isn’t available. 1 is a safe identity default for devices with no natural physical unit (e.g. indexed multi-position sliders); motion-capable subclasses override this with the pulses/mm or pulses/degree value from the model table in the manual. This is always the corrected per-unit value, i.e. after the PULSES_FIELD_IS_PER_REVOLUTION adjustment below has already been applied conceptually.

PULSES_FIELD_IS_PER_REVOLUTION: bool = False

On rotary stages (ELL14/16/18/21/22), the “PULSES/M.U.” field reported by get_info() is empirically the encoder count for one full revolution (e.g. 143360 for the ELL14), not pulses-per-degree as the field name suggests – dividing raw position by it directly yields a 0..1 fraction of the full travel, not degrees. The true pulses-per-degree is that field divided by the reported travel (e.g. 143360 / 360 = 398.2, matching the manual’s documented “398 pulse/deg”). Linear stages (ELL17/ELL20) and the slider/iris families do not show this: their reported field already is pulses/mm or pulses/position, so this stays False for them.

close()[source]

Close the underlying serial port, but only if this device opened it itself.

Return type:

None

property info: DeviceInfo | None

The DeviceInfo from the last successful get_info()/refresh_calibration() call.

Populated automatically at construction time (see __init__), so it’s normally already available – e.g. right after discover_devices() – without an extra round trip. None if the device has never successfully answered an “in” request (e.g. constructed before it was wired up; call refresh_calibration() once it’s reachable).

property serial_number: str | None

Shortcut for info.serial_number; None if info isn’t available yet.

refresh_calibration()[source]

Query the device and cache its pulses-per-unit and full-range pulse counts.

Returns:

The freshly queried device information (also cached as info).

Raises:

ElliptecTimeoutError – If the device doesn’t answer.

Return type:

DeviceInfo

property pulses_per_unit: float

Encoder pulses per mm/degree, from live calibration if available, else the model default.

property pulses_per_full_range: int

encoder pulses across the device’s whole travel.

This is the denominator used by the “_range” methods (e.g. get_position_range), which report position as a 0..1 fraction of full travel instead of physical units. Unlike pulses_per_unit, there’s no static per-model fallback for this – call refresh_calibration() once the device is reachable.

Raises:

ElliptecError – If calibration has never succeeded for this device.

Type:

Raw PULSES/M.U. value from get_info()

get_info()[source]

HOSTREQ_INFORMATION “in” / DEVGET_INFORMATION “IN”.

Returns:

The device’s identification info.

Return type:

DeviceInfo

get_status()[source]

HOSTREQ_STATUS “gs” / DEVGET_STATUS “GS”. Reading the status clears it.

Returns:

The device’s current status/error code.

Return type:

StatusCode

save_user_data()[source]

HOSTREQ_SAVE_USER_DATA “us”. Persists tuned motor/user parameters to EEPROM.

Return type:

None

change_address(new_address)[source]

HOSTREQ_CHANGEADDRESS “ca”. Device replies from the new address.

Parameters:

new_address (str) – The new single hex-digit address to assign.

Raises:

ValueError – If new_address isn’t a valid single hex digit.

Return type:

None

isolate_minutes(minutes)[source]

HOST_ISOLATEMINUTES “is”. Device will not reply for this many minutes.

Parameters:

minutes (int) – How long to isolate the device for.

Return type:

None

group_address(temporary_address)[source]

HOST_GROUPADDRESS “ga”. Device listens on temporary_address until its next move.

Parameters:

temporary_address (str) – Single hex-digit address to listen on temporarily, for synchronizing moves across several devices.

Raises:

ValueError – If temporary_address isn’t a valid single hex digit.

Return type:

None

HOSTREQ_SKIP_FREQUENCY “sk”. Skips the startup frequency scan; needs EEPROM reset to undo.

Return type:

None

get_motor_info(motor)[source]

HOSTREQ_MOTORxINFO “i1”/”i2”.

Parameters:

motor (int) – Which motor to query, 1 or 2.

Returns:

The motor’s current state and tuning.

Raises:
Return type:

MotorInfo

set_forward_period(motor, period)[source]

HOSTSET_FWP_MOTORx “f1”/”f2”. Pass None to restore the factory default.

Parameters:
  • motor (int) – Which motor to tune, 1 or 2.

  • period (int | None) – The raw forward “period” value to set (see period_for_frequency()), or None to restore the factory default.

Raises:
Return type:

None

set_forward_frequency(motor, frequency_hz)[source]

Set a motor’s forward resonant frequency directly, in Hz.

Parameters:
  • motor (int) – Which motor to tune, 1 or 2.

  • frequency_hz (float) – Target forward resonant frequency, in Hz.

Raises:
Return type:

None

set_backward_period(motor, period)[source]

HOSTSET_BWP_MOTORx “b1”/”b2”. Pass None to restore the factory default.

Parameters:
  • motor (int) – Which motor to tune, 1 or 2.

  • period (int | None) – The raw backward “period” value to set (see period_for_frequency()), or None to restore the factory default.

Raises:
Return type:

None

set_backward_frequency(motor, frequency_hz)[source]

Set a motor’s backward resonant frequency directly, in Hz.

Parameters:
  • motor (int) – Which motor to tune, 1 or 2.

  • frequency_hz (float) – Target backward resonant frequency, in Hz.

Raises:
Return type:

None

search_frequency(motor, timeout=30.0)[source]

HOSTREQ_SEARCHFREQ_MOTORx “s1”/”s2”. The moving part may move. Remember to save_user_data().

Parameters:
  • motor (int) – Which motor to search, 1 or 2.

  • timeout (float) – Reply timeout, in seconds.

Returns:

The resulting status code.

Raises:
Return type:

StatusCode

scan_current_curve(motor, timeout=15.0)[source]

HOSTREQ_SCANCURRENTCURVE_MOTORx “c1”/”c2” + DEVGET_CURRENTCURVEMEASURE “C1”/”C2”.

Takes up to ~12 seconds on the device.

Parameters:
  • motor (int) – Which motor to scan, 1 or 2.

  • timeout (float) – Reply timeout, in seconds.

Returns:

Up to 87 (period, current) samples spanning ~70-120 kHz.

Raises:
Return type:

list[CurrentCurveSample]

get_position_pulses(priority=0)[source]

HOST_GETPOSITION “gp” / DEV_GETPOSITION “PO”. Raw encoder pulses, signed.

Parameters:

priority (int) – Forwarded to the bus’s request broker (see RequestPriority); leave it at the default unless this call is opportunistic background polling that shouldn’t delay other, explicitly issued commands (see poll_position).

Returns:

The current position, in raw encoder pulses.

Return type:

int

get_position(priority=0)[source]

Current position in mm or degrees (see pulses_per_unit).

Parameters:

priority (int) – Forwarded to the bus’s request broker; see get_position_pulses().

Returns:

The current position, in mm or degrees.

Return type:

float

get_position_range(priority=0)[source]

Current position as a 0..1 fraction of the device’s full travel.

Parameters:

priority (int) – Forwarded to the bus’s request broker; see get_position_pulses().

Returns:

The current position, as a 0..1 fraction of full travel.

Return type:

float

forward_pulses(timeout=30.0)[source]

HOST_FORWARD “fw”. Moves by the configured jog step (or continuously, see MotionMixin).

Parameters:

timeout (float) – Reply timeout, in seconds.

Returns:

The resulting position, in raw encoder pulses, or None if the reply carried no position (e.g. continuous-motion units).

Return type:

int | None

forward(timeout=30.0)[source]

Jog forward by the configured step size.

Parameters:

timeout (float) – Reply timeout, in seconds.

Returns:

The resulting position, in mm or degrees, or None if the reply carried no position.

Return type:

float | None

backward_pulses(timeout=30.0)[source]

HOST_BACKWARD “bw”.

Parameters:

timeout (float) – Reply timeout, in seconds.

Returns:

The resulting position, in raw encoder pulses, or None if the reply carried no position.

Return type:

int | None

backward(timeout=30.0)[source]

Jog backward by the configured step size.

Parameters:

timeout (float) – Reply timeout, in seconds.

Returns:

The resulting position, in mm or degrees, or None if the reply carried no position.

Return type:

float | None

get_button_status()[source]

Poll once, without blocking indefinitely, for a spontaneous BS/BO message.

These messages are pushed by the device when its physical FWD/BWD/JOG buttons are used; they are not solicited by a host command.

Returns:

The spontaneous reply, or None if nothing arrived within a short timeout.

Raises:

ElliptecUnsupportedError – If this device doesn’t support button status messages.

Return type:

Reply | None

poll_position(interval=0.15, tolerance=0.0, stop_event=None)[source]

Yield the position (mm/degrees), polling roughly every interval seconds.

Only yields when the position has changed by more than tolerance since the last yielded value (default 0: yield on any change). Transient read errors (e.g. a busy status) are swallowed and simply retried on the next tick rather than raised.

Polling requests are issued at RequestPriority.POLL (see tl_elliptec.bus.RequestPriority), so they never delay explicitly issued commands sharing the same bus, including ones for other devices on a multidrop hub.

Pass stop_event (a threading.Event) to stop the loop cleanly from another thread – handy when this generator is being driven by a dedicated polling thread, e.g.:

stop = threading.Event()
t = threading.Thread(target=lambda: [on_position(p) for p in stage.poll_position(stop_event=stop)])
t.start()
...
stop.set()
t.join()

Without stop_event, stop iteration the normal way (break out of a for loop consuming it, or call .close() on the generator from the same thread that’s driving it).

Parameters:
  • interval (float) – Delay between polling ticks, in seconds.

  • tolerance (float) – Minimum change (in mm/degrees) required to yield again; 0 yields on any change.

  • stop_event (Event | None) – Optional event to stop the loop from another thread.

Yields:

The position, in mm or degrees, each time it changes by more than tolerance.

Return type:

Iterator[float]

poll_position_pulses(interval=0.15, tolerance=0.0, stop_event=None)[source]

Like poll_position, but yields raw encoder pulses.

Parameters:
  • interval (float) – Delay between polling ticks, in seconds.

  • tolerance (float) – Minimum change (in raw pulses) required to yield again; 0 yields on any change.

  • stop_event (Event | None) – Optional event to stop the loop from another thread.

Yields:

The position, in raw encoder pulses, each time it changes by more than tolerance.

Return type:

Iterator[int]

poll_position_range(interval=0.15, tolerance=0.0, stop_event=None)[source]

Like poll_position, but yields a 0..1 fraction of the device’s full travel.

Parameters:
  • interval (float) – Delay between polling ticks, in seconds.

  • tolerance (float) – Minimum change (as a 0..1 fraction) required to yield again; 0 yields on any change.

  • stop_event (Event | None) – Optional event to stop the loop from another thread.

Yields:

The position, as a 0..1 fraction of full travel, each time it changes by more than tolerance.

Return type:

Iterator[float]

class tl_elliptec.devices.base.MotionMixin[source]

Bases: object

ho, ma, mr, home-offset, jog-step-size, velocity: not on multi-position sliders.

As with the base class’s position methods, the plain-named methods here (home, move_absolute, move_relative, get_home_offset, set_home_offset, get_jog_step_size, set_jog_step_size) work in physical units (mm or degrees); _pulses-suffixed twins work in raw encoder pulses, and _range-suffixed twins (on home, move_absolute, move_relative) work in a 0..1 fraction of the device’s full travel.

home_pulses(direction=0, timeout=30.0)[source]

HOSTREQ_HOME “ho”.

Parameters:
  • direction (int) – Homing direction for rotary stages: 0 for clockwise, 1 for counter-clockwise. Ignored on other stage types.

  • timeout (float) – Reply timeout, in seconds.

Returns:

The resulting position, in raw encoder pulses, or None if the reply carried no position.

Return type:

int | None

home(direction=0, timeout=30.0)[source]

Move to the home position.

Parameters:
  • direction (int) – Homing direction for rotary stages: 0 for clockwise, 1 for counter-clockwise. Ignored on other stage types.

  • timeout (float) – Reply timeout, in seconds.

Returns:

The resulting position, in mm or degrees, or None if the reply carried no position.

Return type:

float | None

home_range(direction=0, timeout=30.0)[source]

Move to the home position.

Parameters:
  • direction (int) – Homing direction for rotary stages: 0 for clockwise, 1 for counter-clockwise. Ignored on other stage types.

  • timeout (float) – Reply timeout, in seconds.

Returns:

The resulting position, as a 0..1 fraction of full travel, or None if the reply carried no position.

Return type:

float | None

move_absolute_pulses(position, timeout=30.0)[source]

HOSTREQ_MOVEABSOLUTE “ma”.

Parameters:
  • position (int) – Target position, in raw encoder pulses.

  • timeout (float) – Reply timeout, in seconds.

Returns:

The resulting position, in raw encoder pulses, or None if the reply carried no position.

Return type:

int | None

move_absolute(position, timeout=30.0)[source]

Move to an absolute position in mm or degrees (see pulses_per_unit).

Parameters:
  • position (float) – Target position, in mm or degrees.

  • timeout (float) – Reply timeout, in seconds.

Returns:

The resulting position, in mm or degrees, or None if the reply carried no position.

Return type:

float | None

move_absolute_range(fraction, timeout=30.0)[source]

Move to an absolute position given as a 0..1 fraction of the device’s full travel.

Parameters:
  • fraction (float) – Target position, as a 0..1 fraction of full travel.

  • timeout (float) – Reply timeout, in seconds.

Returns:

The resulting position, as a 0..1 fraction of full travel, or None if the reply carried no position.

Return type:

float | None

move_relative_pulses(delta, timeout=30.0)[source]

HOSTREQ_MOVERELATIVE “mr”.

Parameters:
  • delta (int) – Signed distance to move, in raw encoder pulses.

  • timeout (float) – Reply timeout, in seconds.

Returns:

The resulting position, in raw encoder pulses, or None if the reply carried no position.

Return type:

int | None

move_relative(delta, timeout=30.0)[source]

Move by a relative distance in mm or degrees (see pulses_per_unit).

Parameters:
  • delta (float) – Signed distance to move, in mm or degrees.

  • timeout (float) – Reply timeout, in seconds.

Returns:

The resulting position, in mm or degrees, or None if the reply carried no position.

Return type:

float | None

move_relative_range(fraction, timeout=30.0)[source]

Move by a relative distance given as a fraction of the device’s full travel.

Parameters:
  • fraction (float) – Signed distance to move, as a fraction of full travel.

  • timeout (float) – Reply timeout, in seconds.

Returns:

The resulting position, as a 0..1 fraction of full travel, or None if the reply carried no position.

Return type:

float | None

get_home_offset_pulses()[source]

HOSTREQ_HOMEOFFSET “go” / DEVGET_HOMEOFFSET “HO”.

Returns:

Distance of the home position from the absolute limit of travel, in raw encoder pulses.

Return type:

int

get_home_offset()[source]

Distance of the home position from the absolute limit of travel, in units.

Returns:

The home offset, in mm or degrees.

Return type:

float

set_home_offset_pulses(offset)[source]

HOSTSET_HOMEOFFSET “so”.

Parameters:

offset (int) – New home offset, in raw encoder pulses.

Return type:

None

set_home_offset(offset)[source]

Set the distance of the home position from the absolute limit of travel.

Parameters:

offset (float) – New home offset, in mm or degrees.

Return type:

None

get_jog_step_size_pulses()[source]

HOSTREQ_JOGSTEPSIZE “gj” / DEVGET_JOGSTEPSIZE “GJ”.

Returns:

The configured jog step size, in raw encoder pulses.

Return type:

int

get_jog_step_size()[source]

Get the configured jog step size, in mm or degrees.

Returns:

The configured jog step size, in mm or degrees.

Return type:

float

set_jog_step_size_pulses(step)[source]

HOSTSET_JOGSTEPSIZE “sj”. A step of 0 enables continuous motion on ELL14 (use with stop()).

Parameters:

step (int) – New jog step size, in raw encoder pulses.

Return type:

None

set_jog_step_size(step)[source]

Set the jog step size used by forward()/backward().

Parameters:

step (float) – New jog step size, in mm or degrees.

Return type:

None

get_velocity()[source]

HOSTREQ_VELOCITY “gv” / DEVGET_VELOCITY “GV”.

Returns:

The velocity compensation, as a percentage (0-100) of max velocity.

Return type:

int

set_velocity(percent)[source]

HOSTSET_VELOCITY “sv”. 25-45%+ typical minimum before stalling; 50% minimum on ELL16/ELL21.

Parameters:

percent (int) – Velocity compensation, as a percentage (0-100) of max velocity.

Raises:

ValueError – If percent is outside 0-100.

Return type:

None

stop()[source]

HOST_MOTIONSTOP “st”. Stops continuous motion (ELL14) or an optimize/clean cycle.

Confirmed on real hardware: this does not interrupt a bounded move_absolute/move_relative/home once issued – the move keeps running until the physical motion completes, exactly as if stop() was never called. It only affects continuous jog motion (jog step size 0, started via forward/backward) or an in-progress optimize/clean cycle.

Sent as an urgent, queue-bypassing write (see ElliptecBus.send_urgent) rather than a normal request, since the whole point is interrupting a command that’s already in flight – that job’s own read loop is what’s occupying the bus, so a normal request would just queue harmlessly behind it. Doesn’t wait for a reply; call get_status()/get_position() afterward if you need to confirm the outcome.

Return type:

None

class tl_elliptec.devices.base.AutoHomingMixin[source]

Bases: object

ah: ELL15 motorized iris only.

set_auto_homing_pulses(enabled, timeout=30.0)[source]

HOSTSET_AUTOHOMING “ah”. Home-at-startup toggle for the ELL15.

Parameters:
  • enabled (bool) – True to home automatically at startup, False to disable it.

  • timeout (float) – Reply timeout, in seconds.

Returns:

The resulting position, in raw encoder pulses, or None if the reply carried no position.

Return type:

int | None

set_auto_homing(enabled, timeout=30.0)[source]

Home-at-startup toggle for the ELL15.

Parameters:
  • enabled (bool) – True to home automatically at startup, False to disable it.

  • timeout (float) – Reply timeout, in seconds.

Returns:

The resulting position, in units, or None if the reply carried no position.

Return type:

float | None

class tl_elliptec.devices.base.OptimizeCleanMixin[source]

Bases: object

om, cm, st: ELL14/15/16/17/18/20/21/22. supports_clean gates cm (ELL15 lacks it).

supports_clean: bool = True
optimize_motors(timeout=300.0)[source]

HOST_OPTIMIZE_MOTORS “om”. Can take several minutes; occupies the whole bus.

Parameters:

timeout (float) – Reply timeout, in seconds (default allows several minutes for the optimization cycle to complete).

Returns:

The resulting status code.

Return type:

StatusCode

clean_mechanics(timeout=300.0)[source]

HOST_CLEAN_MECHANICS “cm”. Can take several minutes; occupies the whole bus.

Parameters:

timeout (float) – Reply timeout, in seconds (default allows several minutes for the cleaning cycle to complete).

Returns:

The resulting status code.

Raises:

ElliptecUnsupportedError – If this device doesn’t support the cleaning cycle (e.g. the ELL15).

Return type:

StatusCode

stop()[source]

HOST_MOTIONSTOP “st”. Aborts an in-progress optimize/clean cycle.

Sent as an urgent, queue-bypassing write (see ElliptecBus.send_urgent) since the optimize/clean command already in flight is what’s occupying the bus; a normal request would just queue behind it. Doesn’t wait for a reply – call get_status() afterward if you need to confirm the abort.

Return type:

None

class tl_elliptec.devices.base.ZeroPositionMixin[source]

Bases: object

sz, gz: ND filter rotator (ELL22) only.

set_zero_position()[source]

HOSTSET_ZEROPOSITION “sz”. Makes the current encoder position the new logical zero.

Returns:

The resulting status code.

Return type:

StatusCode

get_zero_position_offset_pulses()[source]

HOSTGET_ZEROPOSITION “gz” / DEVGET_ZEROPOSITION “ZO”.

Returns:

The offset between the logical zero and the absolute encoder zero, in raw encoder pulses.

Return type:

int

get_zero_position_offset()[source]

Offset between the logical zero and the absolute encoder zero, in units.

Returns:

The zero-position offset, in mm or degrees.

Return type:

float

class tl_elliptec.devices.base.ResetFactoryMixin[source]

Bases: object

rd: ELL22 only. Restarts the device.

reset_factory_default()[source]

HOSTREQ_RESETFACTORY_DEFAULT “rd”. Restores all parameters to factory defaults and restarts.

Return type:

None

Concrete models

Concrete classes for every ELLx module covered by the protocol manual.

Capability composition follows the per-command “applicability” notes in the manufacturer manual (not the summary index tables at the end of the manual, which are inconsistent with the per-command notes and with each other):

Model     Travel type          Motors  Motion cmds  Optimize/Clean  Extras
ELL6      31mm slider (index)  1                                   -
ELL6B     31mm slider (index)  2                                   -
ELL9      31mm slider (index)  2                                   -
ELL12     19mm slider (index)  2                                   -
ELL14     360 deg rotary       2       yes          both            -
ELL15     iris                 2       yes          optimize only   auto-homing
ELL16     360 deg rotary       2       yes          both            no f/b tuning, no buttons
ELL17     28mm linear          2       yes          both            -
ELL18     360 deg rotary       2       yes          both            -
ELL20     60mm linear          2       yes          both            -
ELL21     360 deg rotary       2       yes          both            no f/b tuning, no buttons
ELL22     360 deg filter rot.  2       yes          both            no f/b tuning, no buttons,
                                                                     zero-position, factory reset
class tl_elliptec.devices.models.ELL6(port_or_bus, address='0', timeout=None, **bus_kwargs)[source]

Bases: ElliptecDevice

Bi-positional slider, 1 motor, 31mm indexed travel.

Parameters:
has_motor2: bool = False

Overridden by subclasses. Some families lack a second motor (ELL6), and ELL16/ELL21/ELL22 do not support tunable forward/backward periods or the hardware-button spontaneous status messages.

class tl_elliptec.devices.models.ELL6B(port_or_bus, address='0', timeout=None, **bus_kwargs)[source]

Bases: ElliptecDevice

Multi-position slider, 2 motors, 31mm indexed travel.

Parameters:
class tl_elliptec.devices.models.ELL9(port_or_bus, address='0', timeout=None, **bus_kwargs)[source]

Bases: ElliptecDevice

Multi-position slider, 2 motors, 31mm indexed travel.

Parameters:
class tl_elliptec.devices.models.ELL12(port_or_bus, address='0', timeout=None, **bus_kwargs)[source]

Bases: ElliptecDevice

Multi-position slider, 2 motors, 19mm indexed travel.

Parameters:
class tl_elliptec.devices.models.ELL14(port_or_bus, address='0', timeout=None, **bus_kwargs)[source]

Bases: MotionMixin, OptimizeCleanMixin, ElliptecDevice

Rotary stage, 360 deg, 398 pulses/deg.

Parameters:
DEFAULT_PULSES_PER_UNIT: float = 398.0

Fallback encoder-pulses-per-unit (per mm or per degree) used by unit-based position methods (move_absolute, get_position, …) when live calibration from get_info() isn’t available. 1 is a safe identity default for devices with no natural physical unit (e.g. indexed multi-position sliders); motion-capable subclasses override this with the pulses/mm or pulses/degree value from the model table in the manual. This is always the corrected per-unit value, i.e. after the PULSES_FIELD_IS_PER_REVOLUTION adjustment below has already been applied conceptually.

PULSES_FIELD_IS_PER_REVOLUTION: bool = True

On rotary stages (ELL14/16/18/21/22), the “PULSES/M.U.” field reported by get_info() is empirically the encoder count for one full revolution (e.g. 143360 for the ELL14), not pulses-per-degree as the field name suggests – dividing raw position by it directly yields a 0..1 fraction of the full travel, not degrees. The true pulses-per-degree is that field divided by the reported travel (e.g. 143360 / 360 = 398.2, matching the manual’s documented “398 pulse/deg”). Linear stages (ELL17/ELL20) and the slider/iris families do not show this: their reported field already is pulses/mm or pulses/position, so this stays False for them.

class tl_elliptec.devices.models.ELL15(port_or_bus, address='0', timeout=None, **bus_kwargs)[source]

Bases: AutoHomingMixin, MotionMixin, OptimizeCleanMixin, ElliptecDevice

Motorized iris.

Parameters:
supports_clean: bool = False
DEFAULT_PULSES_PER_UNIT: float = 1000.0

Fallback encoder-pulses-per-unit (per mm or per degree) used by unit-based position methods (move_absolute, get_position, …) when live calibration from get_info() isn’t available. 1 is a safe identity default for devices with no natural physical unit (e.g. indexed multi-position sliders); motion-capable subclasses override this with the pulses/mm or pulses/degree value from the model table in the manual. This is always the corrected per-unit value, i.e. after the PULSES_FIELD_IS_PER_REVOLUTION adjustment below has already been applied conceptually.

class tl_elliptec.devices.models.ELL16(port_or_bus, address='0', timeout=None, **bus_kwargs)[source]

Bases: MotionMixin, OptimizeCleanMixin, ElliptecDevice

Rotary stage, 360 deg, 182 pulses/deg.

Parameters:
supports_motor_tuning: bool = False
supports_button_messages: bool = False
DEFAULT_PULSES_PER_UNIT: float = 182.0

Fallback encoder-pulses-per-unit (per mm or per degree) used by unit-based position methods (move_absolute, get_position, …) when live calibration from get_info() isn’t available. 1 is a safe identity default for devices with no natural physical unit (e.g. indexed multi-position sliders); motion-capable subclasses override this with the pulses/mm or pulses/degree value from the model table in the manual. This is always the corrected per-unit value, i.e. after the PULSES_FIELD_IS_PER_REVOLUTION adjustment below has already been applied conceptually.

PULSES_FIELD_IS_PER_REVOLUTION: bool = True

On rotary stages (ELL14/16/18/21/22), the “PULSES/M.U.” field reported by get_info() is empirically the encoder count for one full revolution (e.g. 143360 for the ELL14), not pulses-per-degree as the field name suggests – dividing raw position by it directly yields a 0..1 fraction of the full travel, not degrees. The true pulses-per-degree is that field divided by the reported travel (e.g. 143360 / 360 = 398.2, matching the manual’s documented “398 pulse/deg”). Linear stages (ELL17/ELL20) and the slider/iris families do not show this: their reported field already is pulses/mm or pulses/position, so this stays False for them.

class tl_elliptec.devices.models.ELL17(port_or_bus, address='0', timeout=None, **bus_kwargs)[source]

Bases: MotionMixin, OptimizeCleanMixin, ElliptecDevice

Linear stage, 28mm, 1024 pulses/mm.

Parameters:
DEFAULT_PULSES_PER_UNIT: float = 1024.0

Fallback encoder-pulses-per-unit (per mm or per degree) used by unit-based position methods (move_absolute, get_position, …) when live calibration from get_info() isn’t available. 1 is a safe identity default for devices with no natural physical unit (e.g. indexed multi-position sliders); motion-capable subclasses override this with the pulses/mm or pulses/degree value from the model table in the manual. This is always the corrected per-unit value, i.e. after the PULSES_FIELD_IS_PER_REVOLUTION adjustment below has already been applied conceptually.

class tl_elliptec.devices.models.ELL18(port_or_bus, address='0', timeout=None, **bus_kwargs)[source]

Bases: MotionMixin, OptimizeCleanMixin, ElliptecDevice

Rotary stage, 360 deg, 398 pulses/deg.

Parameters:
DEFAULT_PULSES_PER_UNIT: float = 398.0

Fallback encoder-pulses-per-unit (per mm or per degree) used by unit-based position methods (move_absolute, get_position, …) when live calibration from get_info() isn’t available. 1 is a safe identity default for devices with no natural physical unit (e.g. indexed multi-position sliders); motion-capable subclasses override this with the pulses/mm or pulses/degree value from the model table in the manual. This is always the corrected per-unit value, i.e. after the PULSES_FIELD_IS_PER_REVOLUTION adjustment below has already been applied conceptually.

PULSES_FIELD_IS_PER_REVOLUTION: bool = True

On rotary stages (ELL14/16/18/21/22), the “PULSES/M.U.” field reported by get_info() is empirically the encoder count for one full revolution (e.g. 143360 for the ELL14), not pulses-per-degree as the field name suggests – dividing raw position by it directly yields a 0..1 fraction of the full travel, not degrees. The true pulses-per-degree is that field divided by the reported travel (e.g. 143360 / 360 = 398.2, matching the manual’s documented “398 pulse/deg”). Linear stages (ELL17/ELL20) and the slider/iris families do not show this: their reported field already is pulses/mm or pulses/position, so this stays False for them.

class tl_elliptec.devices.models.ELL20(port_or_bus, address='0', timeout=None, **bus_kwargs)[source]

Bases: MotionMixin, OptimizeCleanMixin, ElliptecDevice

Linear stage, 60mm, 1024 pulses/mm.

Parameters:
DEFAULT_PULSES_PER_UNIT: float = 1024.0

Fallback encoder-pulses-per-unit (per mm or per degree) used by unit-based position methods (move_absolute, get_position, …) when live calibration from get_info() isn’t available. 1 is a safe identity default for devices with no natural physical unit (e.g. indexed multi-position sliders); motion-capable subclasses override this with the pulses/mm or pulses/degree value from the model table in the manual. This is always the corrected per-unit value, i.e. after the PULSES_FIELD_IS_PER_REVOLUTION adjustment below has already been applied conceptually.

class tl_elliptec.devices.models.ELL21(port_or_bus, address='0', timeout=None, **bus_kwargs)[source]

Bases: MotionMixin, OptimizeCleanMixin, ElliptecDevice

Rotary stage, 360 deg, 182 pulses/deg.

Parameters:
supports_motor_tuning: bool = False
supports_button_messages: bool = False
DEFAULT_PULSES_PER_UNIT: float = 182.0

Fallback encoder-pulses-per-unit (per mm or per degree) used by unit-based position methods (move_absolute, get_position, …) when live calibration from get_info() isn’t available. 1 is a safe identity default for devices with no natural physical unit (e.g. indexed multi-position sliders); motion-capable subclasses override this with the pulses/mm or pulses/degree value from the model table in the manual. This is always the corrected per-unit value, i.e. after the PULSES_FIELD_IS_PER_REVOLUTION adjustment below has already been applied conceptually.

PULSES_FIELD_IS_PER_REVOLUTION: bool = True

On rotary stages (ELL14/16/18/21/22), the “PULSES/M.U.” field reported by get_info() is empirically the encoder count for one full revolution (e.g. 143360 for the ELL14), not pulses-per-degree as the field name suggests – dividing raw position by it directly yields a 0..1 fraction of the full travel, not degrees. The true pulses-per-degree is that field divided by the reported travel (e.g. 143360 / 360 = 398.2, matching the manual’s documented “398 pulse/deg”). Linear stages (ELL17/ELL20) and the slider/iris families do not show this: their reported field already is pulses/mm or pulses/position, so this stays False for them.

class tl_elliptec.devices.models.ELL22(port_or_bus, address='0', timeout=None, **bus_kwargs)[source]

Bases: ZeroPositionMixin, ResetFactoryMixin, MotionMixin, OptimizeCleanMixin, ElliptecDevice

ND filter rotary stage, 360 deg, 182 pulses/deg.

Parameters:
supports_motor_tuning: bool = False
supports_button_messages: bool = False
DEFAULT_PULSES_PER_UNIT: float = 182.0

Fallback encoder-pulses-per-unit (per mm or per degree) used by unit-based position methods (move_absolute, get_position, …) when live calibration from get_info() isn’t available. 1 is a safe identity default for devices with no natural physical unit (e.g. indexed multi-position sliders); motion-capable subclasses override this with the pulses/mm or pulses/degree value from the model table in the manual. This is always the corrected per-unit value, i.e. after the PULSES_FIELD_IS_PER_REVOLUTION adjustment below has already been applied conceptually.

PULSES_FIELD_IS_PER_REVOLUTION: bool = True

On rotary stages (ELL14/16/18/21/22), the “PULSES/M.U.” field reported by get_info() is empirically the encoder count for one full revolution (e.g. 143360 for the ELL14), not pulses-per-degree as the field name suggests – dividing raw position by it directly yields a 0..1 fraction of the full travel, not degrees. The true pulses-per-degree is that field divided by the reported travel (e.g. 143360 / 360 = 398.2, matching the manual’s documented “398 pulse/deg”). Linear stages (ELL17/ELL20) and the slider/iris families do not show this: their reported field already is pulses/mm or pulses/position, so this stays False for them.

tl_elliptec.devices.models.MODEL_REGISTRY: dict[int, type[ElliptecDevice]] = {6: <class 'tl_elliptec.devices.models.ELL6'>, 9: <class 'tl_elliptec.devices.models.ELL9'>, 12: <class 'tl_elliptec.devices.models.ELL12'>, 14: <class 'tl_elliptec.devices.models.ELL14'>, 15: <class 'tl_elliptec.devices.models.ELL15'>, 16: <class 'tl_elliptec.devices.models.ELL16'>, 17: <class 'tl_elliptec.devices.models.ELL17'>, 18: <class 'tl_elliptec.devices.models.ELL18'>, 20: <class 'tl_elliptec.devices.models.ELL20'>, 21: <class 'tl_elliptec.devices.models.ELL21'>, 22: <class 'tl_elliptec.devices.models.ELL22'>}

Maps the decimal model number (14 for an ELL14, etc.) reported by “in”/”IN” – decoded from the wire’s hex-ASCII “ELL type” field by DeviceInfo.from_reply – to its class. Keyed by the plain decimal number on purpose: the hex-vs-decimal conversion belongs at that one wire boundary, not scattered through code that just wants to know “is this an ELL14”. Note: ELL6 and ELL6B both report type 6 (they differ only in motor count); the factory disambiguates them by probing for a second motor.