Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 50 additions & 6 deletions sentio_prober_control/Sentio/CommandGroups/ProbeCommandGroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def async_step_probe_site_first(self, probe: ProbeSentio) -> int:
return resp.cmd_id()


def get_probe_site(self, probe: ProbeSentio, idx: int) -> Tuple[int, str, float, float]:
def get_probe_site(self, probe: ProbeSentio, idx: int) -> Tuple[int, float, float, str]:
"""Get information for a probe site.

Each positioner can define n a number of predefined positions called "sites".
Expand All @@ -93,13 +93,13 @@ def get_probe_site(self, probe: ProbeSentio, idx: int) -> Tuple[int, str, float,
probe: The probe to get the site for.

Returns:
A tuple containing the site index, the site name, the x position in micrometer and the y position in micrometer.
A tuple containing the site index, position x, the position y in micrometer and the reference.
"""
self.comm.send(f"get_positioner_site {probe.toSentioAbbr()},{idx}")
resp = Response.check_resp(self.comm.read_line())
tok = resp.message().split(",")

return int(tok[0]), str(tok[1]), float(tok[2]), float(tok[3])
return str(tok[0]), float(tok[1]), float(tok[2]), str(tok[3])


def get_probe_site_number(self, probe: ProbeSentio) -> int:
Expand Down Expand Up @@ -144,7 +144,7 @@ def get_probe_z(self, probe: ProbeSentio, ref: ProbeZReference) -> float:
Returns:
The z position in micrometer.
"""
self.comm.send(f"get_positioner_z {probe.toSentioAbbr()}, {ref.toSentioAbbr()}")
self.comm.send(f"get_positioner_z {probe.toSentioAbbr()},{ref.toSentioAbbr()}")
resp = Response.check_resp(self.comm.read_line())
return float(resp.message())

Expand Down Expand Up @@ -226,7 +226,7 @@ def move_probe_z(self, probe: ProbeSentio, ref: ProbeZReference, z: float) -> fl
The z position after the move in micrometer (from zero).
"""

self.comm.send(f"move_positioner_z {probe.toSentioAbbr()}, {ref.toSentioAbbr()}, {z}")
self.comm.send(f"move_positioner_z {probe.toSentioAbbr()},{ref.toSentioAbbr()},{z}")
resp = Response.check_resp(self.comm.read_line())
return float(resp.message())

Expand Down Expand Up @@ -259,7 +259,7 @@ def set_probe_home(self, probe: ProbeSentio, site: ChuckSite | None = None, x: f
if site is None:
self.comm.send(f"set_positioner_home {probe.toSentioAbbr()}")
else:
self.comm.send(f"set_positioner_home {probe.toSentioAbbr()}, {site.toSentioAbbr()}, {x}, {y}")
self.comm.send(f"set_positioner_home {probe.toSentioAbbr()},{site.toSentioAbbr()},{x},{y}")

Response.check_resp(self.comm.read_line())

Expand Down Expand Up @@ -321,3 +321,47 @@ def step_probe_site_next(self, probe: ProbeSentio) -> Tuple[str, float, float]:
resp = Response.check_resp(self.comm.read_line())
tok = resp.message().split(",")
return tok[0], float(tok[1]), float(tok[2])

def enable_probe_motor(self, probe: ProbeSentio, status: bool) -> None:
"""Enable/Disable the probe motor.

Probe with 3 motors will enable and disable by following behavior.

Args:
probe: The probe to action.
status: Enable or disable status

"""
self.comm.send(f"enable_positioner_motor {probe.toSentioAbbr()},{status}")
Response.check_resp(self.comm.read_line())

def get_probe_status(self, probe: ProbeSentio) -> str:
"""Obtain the status of probe.

Command will return 4 probe status

Args:
probe: The probe to step.

Returns:
Status of positioner with 4 digits, 1st digit indicates the East Positioner, 2nd digit indicates West Positioner
3rd digit indicates the North Positioner, 4rd digit indicates the South Positioner.
"""

self.comm.send(f"get_positioner_status {probe.toSentioAbbr()}")
resp = Response.check_resp(self.comm.read_line())
return resp.message()

def set_probe_status(self, probe: ProbeSentio, status: bool) -> None:
"""Enable/Disable the probe stage in the SENTIO.

Enable/Disable the Probes in the SENTIO.

Args:
probe: The probe to action.
status: Enable or disable status

"""
self.comm.send(f"set_positioner_status {probe.toSentioAbbr()},{status}")
Response.check_resp(self.comm.read_line())

223 changes: 220 additions & 3 deletions sentio_prober_control/Sentio/CommandGroups/SiPHCommandGroup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Tuple

from sentio_prober_control.Sentio.Enumerations import ProbeSentio
from sentio_prober_control.Sentio.Enumerations import ProbeSentio, UvwAxis, FiberType
from sentio_prober_control.Sentio.Response import Response
from sentio_prober_control.Sentio.CommandGroups.CommandGroupBase import CommandGroupBase

Expand All @@ -15,11 +15,11 @@ def __init__(self, comm) -> None:
super().__init__(comm)


def fast_alignment(self) -> Response:
def fast_alignment(self) -> None:
"""Perform fast fiber alignment."""

self.comm.send("siph:fast_alignment")
return Response.check_resp(self.comm.read_line())
Response.check_resp(self.comm.read_line())


def get_cap_sensor(self) -> Tuple[float, float]:
Expand Down Expand Up @@ -77,3 +77,220 @@ def move_separation(self, probe: ProbeSentio) -> None:

self.comm.send(f"siph:move_separation {probe.toSentioAbbr()}")
Response.check_resp(self.comm.read_line())

def coupling(self, probe: ProbeSentio, axis: UvwAxis) -> None:
"""Start execute coupling .

Args:
probe: Execute probe.
axis: Execute axis
"""

self.comm.send(f"siph:coupling {probe.toSentioAbbr()},{axis.toSentioAbbr()}")
Response.check_resp(self.comm.read_line())

def get_alignment(self, probe: ProbeSentio, fiber_type: FiberType) -> Tuple[bool, bool, bool, bool]:
"""Get the fast alignment function enable including Coarse, Fine, Gradient, and Rotary/Focal searching.

Args:
probe: The probe to get the alignment settings for.
fiber_type: The type of fiber used (Single, Array, or Lensed).

Returns:
A tuple containing the status of Coarse, Fine, Gradient, and Rotary/Focal searching (True/False).
"""
self.comm.send(f"siph:get_alignment {probe.toSentioAbbr()},{fiber_type.toSentioAbbr()}")
resp = Response.check_resp(self.comm.read_line())

tok = resp.message().split(",")
coarse = tok[0].strip().lower() == "true"
fine = tok[1].strip().lower() == "true"
gradient = tok[2].strip().lower() == "true"
rotary_focal = tok[3].strip().lower() == "true"

return coarse, fine, gradient, rotary_focal

def set_origin(self, probe: ProbeSentio) -> None:
"""Set the current position as the origin position for the SiPH positioner.

Args:
probe: The probe to set the origin position for (East or West).

Returns:
A Response object containing the command execution status.
"""
self.comm.send(f"siph:set_origin {probe.toSentioAbbr()}")
Response.check_resp(self.comm.read_line())

def move_origin(self, probe: ProbeSentio) -> None:
"""Move SiPH positioner to its origin position.

The movement includes:
- NanoCube XY moves back to 50 μm.
- UVW axes move back to the position set during Hover Height training.
- If the axis is in "Manual" mode, only NanoCube moves to 50 μm.

Args:
probe: The probe to move to origin position (East or West).

Returns:
A Response object containing the command execution status.
"""
self.comm.send(f"siph:move_origin {probe.toSentioAbbr()}")
return Response.check_resp(self.comm.read_line())

def move_position_uvw(self, probe: ProbeSentio, axis: UvwAxis, degree: float) -> float:
"""Move the SiPH positioner target axis with a relative degree.

Args:
probe: The positioner ID to move (East or West).
axis: The axis to move (U, V, or W).
degree: The relative degree to move.

Returns:
The current position of the axis after movement.
"""
self.comm.send(f"siph:move_position_uvw {probe.toSentioAbbr()},{axis.toSentioAbbr()},{degree}")
resp = Response.check_resp(self.comm.read_line())

return float(resp.message())

def pivot_point(self, probe: ProbeSentio) -> None:
"""Run pivot point calibration for the specified positioner.

Args:
probe: The positioner ID to calibrate (East or West).

Returns:
A Response object containing the command execution status.
"""
self.comm.send(f"siph:pivot_point {probe.toSentioAbbr()}")
Response.check_resp(self.comm.read_line())

def set_alignment(self, probe: ProbeSentio, fiber_type: FiberType, coarse: bool, fine: bool, gradient: bool,
rotary: bool) -> None:
"""Set the fast alignment function enable including Coarse, Fine, Gradient, and Rotary/Focal searching.

Args:
probe: The positioner ID to calibrate (East or West).
fiber_type: The fiber type ("Single", "Array", "Lensed").
coarse: Enable or disable coarse search (True = ON, False = OFF).
fine: Enable or disable fine search (True = ON, False = OFF).
gradient: Enable or disable gradient search (True = ON, False = OFF).
rotary: Enable or disable rotary/focal search (True = ON, False = OFF, not supported for "Single" fiber type).

Returns:
A Response object containing the command execution status.
"""
coarse_str = "ON" if coarse else "OFF"
fine_str = "ON" if fine else "OFF"
gradient_str = "ON" if gradient else "OFF"
rotary_str = "ON" if rotary else "OFF"

self.comm.send(
f"siph:set_alignment {probe.toSentioAbbr()},{fiber_type},{coarse_str},{fine_str},{gradient_str},{rotary_str}")
Response.check_resp(self.comm.read_line())

def set_pivot_point(self, rotary_angle_1: float, rotary_angle_2: float, leveling_angle: float, repeats: int) -> None:
"""Set the parameters for the pivot point function.

Args:
rotary_angle_1: The first rotary angle.
rotary_angle_2: The second rotary angle.
leveling_angle: The leveling angle.
repeats: The number of repetitions.

Returns:
A Response object containing the command execution status.
"""
self.comm.send(f"siph:set_pivot_point {rotary_angle_1},{rotary_angle_2},{leveling_angle},{repeats}")
Response.check_resp(self.comm.read_line())

def download_graph_data(self, file_path: str, file_name: str) -> None:
"""Download the graph data and save it to the specified location.

Args:
file_path: The directory path where the graph data will be saved.
file_name: The name of the file to save the graph data.

Returns:
A Response object containing the command execution status.
"""
self.comm.send(f"siph:download_graph_data {file_path}, {file_name}")
Response.check_resp(self.comm.read_line())

def start_tracking(self, timeout: int = 60) -> int:
"""Start the SiPH positioner gradient tracking search asynchronously.

Args:
timeout: Timeout value in seconds (range: 1~600). Default is 60 sec.

Returns:
The asynchronous command ID, which can be used to check status or abort.
"""
self.comm.send(f"siph:start_tracking {timeout}")
resp = Response.check_resp(self.comm.read_line())

# Extract asynchronous command ID from response
command_id = int(resp.cmd_id())
return command_id

def move_nanocube_xy(self, probe: ProbeSentio, x: float, y: float) -> tuple[float, float]:
"""Move NanoCube to the target XY position.

The movement range is limited to 0 ~ 100 μm.

Args:
probe: The positioner (East or West).
x: Target X position (μm), must be in range [0, 100].
y: Target Y position (μm), must be in range [0, 100].

Returns:
A tuple containing the new X and Y positions after movement.
"""
if not (0 <= x <= 100 and 0 <= y <= 100):
raise ValueError("X and Y values must be between 0 and 100 μm.")

self.comm.send(f"siph:move_nanocube_xy {probe.toSentioAbbr()},{x},{y}")
resp = Response.check_resp(self.comm.read_line())

# Parse response message
tok = resp.message().split(",")
new_x = float(tok[0])
new_y = float(tok[1])
return new_x, new_y

def get_nanocube_xy(self, probe: ProbeSentio) -> tuple[float, float]:
"""Get the current NanoCube XY position.

Args:
probe: The positioner (East or West).

Returns:
A tuple containing the current X and Y positions.
"""
self.comm.send(f"siph:get_nanocube_xy {probe.toSentioAbbr()}")
resp = Response.check_resp(self.comm.read_line())

# Parse response message
tok = resp.message().split(",")
current_x = float(tok[0])
current_y = float(tok[1])
return current_x, current_y

def get_nanocube_z(self, probe: ProbeSentio) -> float:
"""Get the current NanoCube Z position.

Args:
probe: The positioner (East or West).

Returns:
The current Z position.
"""
self.comm.send(f"siph:get_nanocube_z {probe.toSentioAbbr()}")
resp = Response.check_resp(self.comm.read_line())

# Parse response message
tok = resp.message().split(",")
current_z = float(tok[0])
return current_z
42 changes: 42 additions & 0 deletions sentio_prober_control/Sentio/Enumerations.py
Original file line number Diff line number Diff line change
Expand Up @@ -1550,3 +1550,45 @@ def toSentioAbbr(self):
ZPositionHint.Transfer: "Transfer",
}
return switcher.get(self, "Invalid ZPositionHint")

class UvwAxis(Enum):
"""An enumeration containing UVW axis.

Attributes:
U (0): U axis.
V (1): V axis.
W (2): W axis.
"""

U = 0
V = 1
W = 2

def toSentioAbbr(self):
switcher = {
UvwAxis.U: "U",
UvwAxis.V: "V",
UvwAxis.W: "W",
}
return switcher.get(self, "Invalid UVW enumerator")

class FiberType(Enum):
"""An enumeration containing supported fiber type.

Attributes:
Single (0)
Array (1)
Lensed (2)
"""

Single = 0
Array = 1
Lensed = 2

def toSentioAbbr(self):
switcher = {
FiberType.Single: "Single",
FiberType.Array: "Array",
FiberType.Lensed: "Lensed",
}
return switcher.get(self, "Invalid fiber type enumerator")
Loading