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
15 changes: 10 additions & 5 deletions src/controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,14 @@ def _create_axis_column(index: int, axis: Any) -> None:
axis_state, ctr_cfg, mtr_cfg, enc_cfg, trp_cfg = mirrors
cc = axis.motor.current_control

async def sync_mirrors() -> None:
for mirror in mirrors:
await mirror.sync()
async def sync_state() -> None:
await axis_state.sync()
show_state(axis_state.current_state)

async def sync_config() -> None:
for mirror in mirrors[1:]:
await mirror.sync()

async def update_power() -> None:
if (watts := await _on_usb(axis, lambda: cc.Iq_measured * cc.v_current_control_integral_q)) is not None:
power.set_text(f'{watts:.1f} W')
Expand All @@ -239,9 +242,11 @@ async def update_error() -> None:
button.set_enabled(error != 0)

# every property read is a USB round-trip, so keep the 10 Hz path to the two values
# that must feel live; the error flag only enables a button and can follow at 1 Hz.
# that must feel live. The error flag only enables a button, and config values only
# change when someone edits them (an edit here shows at once), so they follow at 1 Hz.
ui.timer(0.1, update_power)
ui.timer(0.1, sync_mirrors)
ui.timer(0.1, sync_state)
ui.timer(1.0, sync_config)
ui.timer(1.0, update_error)

def request_state(state: int) -> None:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ async def test_gain_edits_reach_the_device_and_device_changes_reach_the_ui(user:
assert field.value == 42.0

config.pos_gain = 7.0 # changed behind the UI's back (odrivetool, another client)
await asyncio.sleep(1.2)
await asyncio.sleep(2.0) # config values are refreshed at 1 Hz
assert field.value == 7.0


Expand Down
12 changes: 12 additions & 0 deletions tests/test_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,18 @@ async def test_live_values_are_not_read_on_the_event_loop(user: User) -> None:
assert READS['odrive-usb_0'] > before['odrive-usb_0'] # the live values are still read, on the worker


async def test_config_values_are_not_polled_at_10_hz(user: User) -> None:
"""Every worker read delays the writes and commands queued behind it. Only the axis
state and the power label must feel live; the ~22 bound config values per axis are
refreshed at 1 Hz (two calibrated axes: ~105 reads per second instead of ~480)."""
await user.open('/')
await user.should_see('Axis 1')
await asyncio.sleep(0.5) # past the reads of the panel build
before = READS['odrive-usb_0']
await asyncio.sleep(2.0)
assert (READS['odrive-usb_0'] - before) / 2.0 < 150


async def test_a_refresh_that_read_before_an_edit_does_not_undo_it(user: User, mock_odrive) -> None:
"""A refresh reads on the USB worker while the user may edit the same value: the read
sees the old device value, and applying it would snap the widget back -- whose change
Expand Down
Loading