CI for csaxs_bec / test (push) Successful in 1m57s
_oshield_is_ST_open() reads a digital switch (@IN[8014]) on otransx's Galil controller to confirm the sample-stage thermal shield reached its physical "open" limit -- but oshield is a Smaract stage, simulated by a completely separate simulator (sim_smaract.py), so that switch had no connection to oshield's actual simulated position and always read a stale default. sim_galil.py's @IN[8014] handler now computes the switch value live from oshield's real position via SimStateRegistry (cross-simulator lookup), instead of a disconnected default -- 1.0 within tolerance of the "open" target (-11.88, _oshield_ST_open()'s own umv() target), 0.0 otherwise. Also fixes oshield's simulated starting position: it started at 0 (neither the "open" -11.88 nor "closed" 15.5), so _otransfer_stage_to_ST()'s oshield_current < 15 closed-check always failed fresh. Now starts at 15.5, matching the real idle/closed resting state. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Mupip8x7Hean6E3FFvpbj
119 lines
3.8 KiB
Python
119 lines
3.8 KiB
Python
import pytest
|
|
from bec_server.device_server.tests.utils import DMMock
|
|
|
|
from csaxs_bec.devices.omny.galil.ogalil_ophyd import OMNYGalilController
|
|
from csaxs_bec.devices.sim.sim_galil import (
|
|
_OSHIELD_GALIL_HOST,
|
|
_OSHIELD_GALIL_PORT,
|
|
_OSHIELD_OPEN_POSITION_MM,
|
|
_OSHIELD_SMARACT_AXIS,
|
|
_OSHIELD_SMARACT_PORT,
|
|
)
|
|
from csaxs_bec.devices.sim.sim_omny import SimOMNYGalilMotor
|
|
from csaxs_bec.devices.sim.sim_smaract import SimSmaractMotor, SimSmaractState
|
|
from csaxs_bec.devices.sim.sim_socket import SimStateRegistry
|
|
|
|
# otransx's own axis letter doesn't matter here -- only its host/port (shared with
|
|
# oshield's "open" limit switch on real hardware, see sim_galil.py's
|
|
# _OSHIELD_GALIL_HOST/_OSHIELD_GALIL_PORT).
|
|
_OTRANSX_AXIS_ID = "D"
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _reset_sim_state():
|
|
OMNYGalilController._reset_controller()
|
|
SimStateRegistry.reset()
|
|
yield
|
|
SimStateRegistry.reset()
|
|
OMNYGalilController._reset_controller()
|
|
|
|
|
|
def _make_devices(oshield_initial_position):
|
|
otransx = SimOMNYGalilMotor(
|
|
_OTRANSX_AXIS_ID,
|
|
name="otransx",
|
|
host=_OSHIELD_GALIL_HOST,
|
|
port=_OSHIELD_GALIL_PORT,
|
|
sign=1,
|
|
device_manager=DMMock(),
|
|
sim_initial_position=0,
|
|
sim_velocity=10.0,
|
|
)
|
|
otransx.wait_for_connection()
|
|
oshield = SimSmaractMotor(
|
|
"C",
|
|
name="oshield",
|
|
host=_OSHIELD_GALIL_HOST,
|
|
port=_OSHIELD_SMARACT_PORT,
|
|
sign=1,
|
|
device_manager=DMMock(),
|
|
sim_initial_position=oshield_initial_position,
|
|
sim_velocity=10.0,
|
|
)
|
|
oshield.wait_for_connection()
|
|
return otransx, oshield
|
|
|
|
|
|
def _switch_value(otransx) -> float:
|
|
return float(otransx.controller.socket_put_and_receive("MG @IN[8014]"))
|
|
|
|
|
|
def test_switch_reads_closed_when_oshield_is_closed():
|
|
"""oshield closed (15.5, _oshield_ST_close()'s own target) -> switch not
|
|
triggered, matching _oshield_is_ST_open()'s real-hardware semantics."""
|
|
otransx, oshield = _make_devices(15.5)
|
|
try:
|
|
assert _switch_value(otransx) == 0.0
|
|
finally:
|
|
otransx.destroy()
|
|
oshield.destroy()
|
|
|
|
|
|
def test_switch_reads_open_when_oshield_is_at_open_position():
|
|
otransx, oshield = _make_devices(_OSHIELD_OPEN_POSITION_MM)
|
|
try:
|
|
assert _switch_value(otransx) == 1.0
|
|
finally:
|
|
otransx.destroy()
|
|
oshield.destroy()
|
|
|
|
|
|
def test_switch_tracks_oshield_position_live_not_just_at_startup():
|
|
"""The switch must reflect oshield's *current* simulated position, not
|
|
just whatever it started at -- this is the actual bug being fixed:
|
|
the digital switch and oshield's position are simulated by two
|
|
different simulator classes (Galil vs Smaract) that don't otherwise
|
|
talk to each other."""
|
|
otransx, oshield = _make_devices(15.5)
|
|
try:
|
|
assert _switch_value(otransx) == 0.0
|
|
state = SimStateRegistry.get(SimSmaractState, _OSHIELD_GALIL_HOST, _OSHIELD_SMARACT_PORT)
|
|
state.axis(_OSHIELD_SMARACT_AXIS).pos_nm = _OSHIELD_OPEN_POSITION_MM * 1.0e6
|
|
assert _switch_value(otransx) == 1.0
|
|
finally:
|
|
otransx.destroy()
|
|
oshield.destroy()
|
|
|
|
|
|
def test_switch_reads_closed_at_an_intermediate_position():
|
|
otransx, oshield = _make_devices(0.0)
|
|
try:
|
|
assert _switch_value(otransx) == 0.0
|
|
finally:
|
|
otransx.destroy()
|
|
oshield.destroy()
|
|
|
|
|
|
def test_other_channels_unaffected_by_the_oshield_special_case():
|
|
"""A different digital-input channel on the same controller must still
|
|
use the plain seeded/default value, not get swept up in the oshield
|
|
special-case."""
|
|
otransx, oshield = _make_devices(15.5)
|
|
try:
|
|
# channel 14 (light curtain) has its own module-level default (1.0),
|
|
# independent of oshield's position
|
|
assert float(otransx.controller.socket_put_and_receive("MG @IN[14]")) == 1.0
|
|
finally:
|
|
otransx.destroy()
|
|
oshield.destroy()
|