Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hardware-testing): flex stacker diagnostic script for axes #16898

Merged
merged 12 commits into from
Nov 20, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@

from . import (
test_connectivity,
test_z_axis,
test_x_axis,
test_l_axis,
)


class TestSection(enum.Enum):
"""Test Section."""

CONNECTIVITY = "CONNECTIVITY"
Z_AXIS = "Z_AXIS"
L_AXIS = "L_AXIS"
X_AXIS = "X_AXIS"


@dataclass
Expand All @@ -29,6 +35,18 @@ class TestConfig:
TestSection.CONNECTIVITY,
test_connectivity.run,
),
(
TestSection.Z_AXIS,
test_z_axis.run,
),
(
TestSection.L_AXIS,
test_l_axis.run,
),
(
TestSection.X_AXIS,
test_x_axis.run,
),
]


Expand All @@ -40,6 +58,18 @@ def build_report(test_name: str) -> CSVReport:
CSVSection(
title=TestSection.CONNECTIVITY.value,
lines=test_connectivity.build_csv_lines(),
)
),
CSVSection(
title=TestSection.Z_AXIS.value,
lines=test_z_axis.build_csv_lines(),
),
CSVSection(
title=TestSection.L_AXIS.value,
lines=test_l_axis.build_csv_lines(),
),
CSVSection(
title=TestSection.X_AXIS.value,
lines=test_x_axis.build_csv_lines(),
),
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,53 @@ class StackerInfo:
sn: str


class StackerAxis(Enum):
"""Stacker Axis."""

X = "X"
Z = "Z"
L = "L"

def __str__(self) -> str:
"""Name."""
return self.name


class Direction(Enum):
"""Direction."""

RETRACT = 0
EXTENT = 1

def __str__(self) -> str:
"""Convert to tag for clear logging."""
return "negative" if self == Direction.RETRACT else "positive"

def opposite(self) -> "Direction":
"""Get opposite direction."""
return Direction.EXTENT if self == Direction.RETRACT else Direction.RETRACT

def distance(self, distance: float) -> float:
"""Get signed distance, where retract direction is negative."""
return distance * -1 if self == Direction.RETRACT else distance


@dataclass
class MoveParams:
"""Move Parameters."""

max_speed: float | None = None
acceleration: float | None = None
max_speed_discont: float | None = None

def __str__(self) -> str:
"""Convert to string."""
v = "V:" + str(self.max_speed) if self.max_speed else ""
a = "A:" + str(self.acceleration) if self.acceleration else ""
d = "D:" + str(self.max_speed_discont) if self.max_speed_discont else ""
return f"{v} {a} {d}".strip()


class FlexStacker:
"""FLEX Stacker Driver."""

Expand Down Expand Up @@ -87,6 +134,66 @@ def set_serial_number(self, sn: str) -> None:
return
self._send_and_recv(f"M996 {sn}\n", "M996 OK")

def get_limit_switch(self, axis: StackerAxis, direction: Direction) -> bool:
"""Get limit switch status.

:return: True if limit switch is triggered, False otherwise
"""
if self._simulating:
return True

_LS_RE = re.compile(rf"^M119 .*{axis.name}{direction.name[0]}:(\d) .* OK\n")
res = self._send_and_recv("M119\n", "M119 XE:")
match = _LS_RE.match(res)
assert match, f"Incorrect Response for limit switch: {res}"
return bool(int(match.group(1)))

def get_platform_sensor(self, direction: Direction) -> bool:
"""Get platform sensor status.

:return: True if platform is present, False otherwise
"""
if self._simulating:
return True

_LS_RE = re.compile(rf"^M121 .*{direction.name[0]}:(\d) .* OK\n")
res = self._send_and_recv("M121\n", "M119 E:")
match = _LS_RE.match(res)
assert match, f"Incorrect Response for platform sensor: {res}"
return bool(int(match.group(1)))

def get_hopper_door_closed(self) -> bool:
"""Get whether or not door is closed.

:return: True if door is closed, False otherwise
"""
if self._simulating:
return True

_LS_RE = re.compile(r"^M122 (\d) OK\n")
res = self._send_and_recv("M122\n", "M122 ")
match = _LS_RE.match(res)
assert match, f"Incorrect Response for hopper door switch: {res}"
return bool(int(match.group(1)))

def move_in_mm(
self, axis: StackerAxis, distance: float, params: MoveParams | None = None
) -> None:
"""Move axis."""
if self._simulating:
return
self._send_and_recv(f"G0 {axis.name}{distance} {params or ''}\n", "G0 OK")

def move_to_limit_switch(
self, axis: StackerAxis, direction: Direction, params: MoveParams | None = None
) -> None:
"""Move until limit switch is triggered."""
if self._simulating:
return
self._send_and_recv(
f"G5 {axis.name}{direction.value} {params or ''}\n", "G0 OK"
)

def __del__(self) -> None:
"""Close serial port."""
if not self._simulating:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Test L Axis."""
from typing import List, Union
from hardware_testing.data import ui
from hardware_testing.data.csv_report import (
CSVReport,
CSVLine,
CSVLineRepeating,
CSVResult,
)

from .driver import FlexStacker, StackerAxis, Direction


class LimitSwitchError(Exception):
"""Limit Switch Error."""

pass


def build_csv_lines() -> List[Union[CSVLine, CSVLineRepeating]]:
"""Build CSV Lines."""
return [
CSVLine("trigger-latch-switch", [CSVResult]),
CSVLine("release/open-latch", [CSVResult]),
CSVLine("hold/close-latch", [CSVResult]),
]


def get_latch_held_switch(driver: FlexStacker) -> bool:
"""Get limit switch."""
held_switch = driver.get_limit_switch(StackerAxis.L, Direction.RETRACT)
print("(Held Switch triggered) : ", held_switch)
return held_switch


def close_latch(driver: FlexStacker) -> None:
"""Close latch."""
driver.move_to_limit_switch(StackerAxis.L, Direction.EXTENT)


def open_latch(driver: FlexStacker) -> None:
"""Open latch."""
driver.move_in_mm(StackerAxis.L, -22)


def run(driver: FlexStacker, report: CSVReport, section: str) -> None:
"""Run."""
if not get_latch_held_switch(driver):
print("Switch is not triggered, try to trigger it by closing latch...")
close_latch(driver)
if not get_latch_held_switch(driver):
print("!!! Held switch is still not triggered !!!")
report(section, "trigger-latch-switch", [CSVResult.FAIL])
return

report(section, "trigger-latch-switch", [CSVResult.PASS])

ui.print_header("Latch Release/Open")
open_latch(driver)
success = not get_latch_held_switch(driver)
report(section, "release/open-latch", [CSVResult.from_bool(success)])

ui.print_header("Latch Hold/Close")
if not success:
print("Latch must be open to close it")
report(section, "hold/close-latch", [CSVResult.FAIL])
else:
close_latch(driver)
success = get_latch_held_switch(driver)
report(section, "hold/close-latch", [CSVResult.from_bool(success)])
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Test X Axis."""
from typing import List, Union
from hardware_testing.data import ui
from hardware_testing.data.csv_report import (
CSVReport,
CSVLine,
CSVLineRepeating,
CSVResult,
)

from .utils import test_limit_switches_per_direction
from .driver import FlexStacker, StackerAxis, Direction


def build_csv_lines() -> List[Union[CSVLine, CSVLineRepeating]]:
"""Build CSV Lines."""
return [
CSVLine(
"limit-switch-trigger-positive-untrigger-negative", [bool, bool, CSVResult]
),
CSVLine(
"limit-switch-trigger-negative-untrigger-positive", [bool, bool, CSVResult]
),
CSVLine(
"platform-sensor-trigger-positive-untrigger-negative",
[bool, bool, CSVResult],
),
CSVLine(
"platform-sensor-trigger-negative-untrigger-positive",
[bool, bool, CSVResult],
),
]


def test_platform_sensors_for_direction(
driver: FlexStacker, direction: Direction, report: CSVReport, section: str
) -> None:
"""Test platform sensors for a given direction."""
ui.print_header(f"Platform Sensor - {direction} direction")
sensor_result = driver.get_platform_sensor(direction)
opposite_result = not driver.get_platform_sensor(direction.opposite())
print(f"{direction} sensor triggered: {sensor_result}")
print(f"{direction.opposite()} sensor untriggered: {opposite_result}")
report(
section,
f"platform-sensor-trigger-{direction}-untrigger-{direction.opposite()}",
[
sensor_result,
opposite_result,
CSVResult.from_bool(sensor_result and opposite_result),
],
)


def platform_is_removed(driver: FlexStacker) -> bool:
"""Check if the platform is removed from the carrier."""
plus_side = driver.get_platform_sensor(Direction.EXTENT)
minus_side = driver.get_platform_sensor(Direction.RETRACT)
return not plus_side and not minus_side


def run(driver: FlexStacker, report: CSVReport, section: str) -> None:
"""Run."""
if not driver._simulating and not platform_is_removed(driver):
print("FAILURE - Cannot start tests with platform on the carrier")
return

test_limit_switches_per_direction(
driver, StackerAxis.X, Direction.EXTENT, report, section
)

if not driver._simulating:
ui.get_user_ready("Place the platform on the X carrier")

test_platform_sensors_for_direction(driver, Direction.EXTENT, report, section)

test_limit_switches_per_direction(
driver, StackerAxis.X, Direction.RETRACT, report, section
)

test_platform_sensors_for_direction(driver, Direction.RETRACT, report, section)
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Test Z Axis."""
from typing import List, Union
from hardware_testing.data.csv_report import (
CSVReport,
CSVLine,
CSVLineRepeating,
CSVResult,
)

from .utils import test_limit_switches_per_direction
from .driver import FlexStacker, StackerAxis, Direction


def build_csv_lines() -> List[Union[CSVLine, CSVLineRepeating]]:
"""Build CSV Lines."""
return [
CSVLine(
"limit-switch-trigger-positive-untrigger-negative", [bool, bool, CSVResult]
),
CSVLine(
"limit-switch-trigger-negative-untrigger-positive", [bool, bool, CSVResult]
),
]


def run(driver: FlexStacker, report: CSVReport, section: str) -> None:
"""Run."""
test_limit_switches_per_direction(
driver, StackerAxis.Z, Direction.EXTENT, report, section
)

test_limit_switches_per_direction(
driver, StackerAxis.Z, Direction.RETRACT, report, section
)
Loading
Loading