Skip to content

Commit

Permalink
feat(hardware-testing): flex stacker diagnostic script for axes (#16898)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahiuchingau authored Nov 20, 2024
1 parent 0dacfb3 commit a34f2be
Show file tree
Hide file tree
Showing 6 changed files with 361 additions and 1 deletion.
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

0 comments on commit a34f2be

Please sign in to comment.