"""The axis event log: only changes, a baseline on first sight, nothing across a gap.""" from __future__ import annotations from orisys_autocal.motion import AxisState, Snapshot from orisys_autocal.motion import events as ev from orisys_autocal.motion.models import AxisStatusBits from orisys_autocal.motion.events import AxisEventTracker def _axis(axis=0, *, alarm=False, enabled=True, read_ok=True, homing=False, home_status=0, units=10.0, error=""): raw = (512 if alarm else 0) | (64 if homing else 0) return AxisState( axis=axis, enabled=enabled, units=units, status=AxisStatusBits(raw), home_status=home_status, read_ok=read_ok, error=error, ) def _snap(*axes, connected=True) -> Snapshot: return Snapshot(connected=connected, axes=tuple(axes)) def _kinds(events): return [(event.axis, event.kind) for event in events] def test_the_first_poll_is_a_baseline_and_a_steady_state_is_silent(): tracker = AxisEventTracker(clock=lambda: 1.0) assert tracker.update(_snap(_axis(0), _axis(1))) == [] assert tracker.update(_snap(_axis(0), _axis(1))) == [] assert tracker.events == [] def test_an_alarm_that_is_already_up_when_first_seen_is_reported(): tracker = AxisEventTracker() assert _kinds(tracker.update(_snap(_axis(2, alarm=True)))) == [(2, ev.ALARM_ON)] def test_alarm_enable_and_homing_edges_are_reported_once(): tracker = AxisEventTracker() tracker.update(_snap(_axis(0, enabled=False))) assert _kinds(tracker.update(_snap(_axis(0, enabled=True)))) == [(0, ev.ENABLED)] assert _kinds(tracker.update(_snap(_axis(0, alarm=True)))) == [(0, ev.ALARM_ON)] assert tracker.update(_snap(_axis(0, alarm=True))) == [] assert _kinds(tracker.update(_snap(_axis(0)))) == [(0, ev.ALARM_OFF)] assert _kinds(tracker.update(_snap(_axis(0, homing=True)))) == [(0, ev.HOME_STARTED)] assert _kinds(tracker.update(_snap(_axis(0, home_status=1)))) == [(0, ev.HOME_DONE)] tracker.update(_snap(_axis(0, homing=True))) assert _kinds(tracker.update(_snap(_axis(0, home_status=0)))) == [(0, ev.HOME_INCOMPLETE)] def test_a_read_failure_and_its_recovery_are_reported(): tracker = AxisEventTracker() tracker.update(_snap(_axis(1))) failed = tracker.update(_snap(_axis(1, read_ok=False, error="timeout"))) assert [(e.axis, e.kind, e.detail) for e in failed] == [(1, ev.READ_FAILED, "timeout")] assert tracker.update(_snap(_axis(1, read_ok=False, error="timeout"))) == [] assert _kinds(tracker.update(_snap(_axis(1)))) == [(1, ev.READ_OK)] def test_a_gap_in_the_connection_resets_the_baseline(): tracker = AxisEventTracker() tracker.update(_snap(_axis(0))) assert _kinds(tracker.update(_snap(connected=False))) == [(ev.NO_AXIS, ev.DISCONNECTED)] # Back again with the axis alarmed: the connection edge, then the alarm as a # first sight; nothing is compared with a state from before the gap. back = tracker.update(_snap(_axis(0, alarm=True))) assert _kinds(back) == [(ev.NO_AXIS, ev.CONNECTED), (0, ev.ALARM_ON)] def test_units_disagreeing_with_the_configuration_is_an_event_both_ways(): tracker = AxisEventTracker() expected = {0: 10.0} assert tracker.update(_snap(_axis(0, units=10.0)), expected) == [] mismatch = tracker.update(_snap(_axis(0, units=5.0)), expected) assert [(e.kind, e.detail) for e in mismatch] == [(ev.UNITS_MISMATCH, "10|5")] assert tracker.update(_snap(_axis(0, units=5.0)), expected) == [] assert _kinds(tracker.update(_snap(_axis(0, units=10.0)), expected)) == [(0, ev.UNITS_OK)] # With nothing to compare against no verdict is invented. assert AxisEventTracker().update(_snap(_axis(0, units=5.0))) == [] def test_the_log_is_bounded_and_can_be_cleared(): tracker = AxisEventTracker(limit=3) tracker.update(_snap(_axis(0))) for index in range(10): tracker.update(_snap(_axis(0, alarm=bool(index % 2 == 0)))) assert len(tracker.events) == 3 tracker.clear() assert tracker.events == []