Source code for ui.ui_gains
"""
Controller gain tuning wizard for the Romi robot UI.
Steps the user through adjusting five control parameters via the serial
terminal, then persists all gains to ``GAINS_FILE`` so they survive a
power cycle. Uses ``multichar_input`` for non-blocking numeric entry.
Parameters tuned (in order):
1. Motor Kp – proportional gain (must be > 0; same value applied to both motors)
2. Motor Ki – integral gain (must be ≥ 0)
3. LF Kp – line-follower proportional gain (must be ≥ 0)
4. LF Ki – line-follower integral gain (must be ≥ 0)
5. LF Kff – line-follower feed-forward gain (must be ≥ 0)
"""
from multichar_input import multichar_input
from constants import GAINS_FILE
[docs]
def run(ui):
"""
Generator that runs the interactive gain-tuning wizard.
For each gain the function displays the current value, prompts for a new
one, validates the range, and either updates the shared variable or
reports why the entry was rejected. A summary table is printed at the
end and all gains are saved to disk.
Uses ``yield from multichar_input(ui._ser)`` so the scheduler remains
responsive while waiting for the user to type a value.
Args:
ui: UI context object exposing ``_ser``, ``_leftMotorKp``,
``_rightMotorKp``, ``_leftMotorKi``, ``_rightMotorKi``,
``_lineFollowKp``, ``_lineFollowKi``, ``_lineFollowKff``
shares/queues, and a ``_save_gains()`` method.
Yields:
Delegates to ``multichar_input`` which yields on every serial poll.
"""
# Prompt and read new motor Kp (assumes same gain for L/R)
ui._ser.write(
f"Current motor Kp gain: {ui._leftMotorKp.get():.2f}\r\n"
)
ui._ser.write("Enter a new motor Kp gain value: \r\n->: ")
value = yield from multichar_input(ui._ser)
if value is not None:
if value <= 0:
ui._ser.write("Invalid motor Kp gain (<= 0). Motor Kp gain unchanged")
else:
# Apply same Kp to both motors (preserved behavior)
ui._leftMotorKp.put(value)
ui._rightMotorKp.put(value)
ui._ser.write(f"Motor Kp Gain Set To: {value:.2f}\r\n")
else:
ui._ser.write("No value entered. Motor Kp gains unchanged.\r\n")
# Prompt and read new motor Ki (assumes same gain for L/R)
ui._ser.write(
f"\r\nCurrent motor Ki gains: {ui._leftMotorKi.get():.2f}\r\n"
)
ui._ser.write("Enter a new motor Ki gain value:\r\n->: ")
value = yield from multichar_input(ui._ser)
if value is not None:
if value < 0:
ui._ser.write("Invalid motor Ki gain (< 0). Motor Ki gains unchanged")
else:
ui._leftMotorKi.put(value)
ui._rightMotorKi.put(value)
ui._ser.write(f"Motor Ki Gain Set To: {value} \r\n")
else:
ui._ser.write("No value entered. Motor Ki gains unchanged.\r\n")
# Prompt and read new line follower Kp
ui._ser.write(
f"\r\nCurrent line follower (LF) Kp gain: {ui._lineFollowKp.get():.2f}\r\n"
)
ui._ser.write("Enter a new LF Kp gain value:\r\n->: ")
value = yield from multichar_input(ui._ser)
if value is not None:
if value < 0:
ui._ser.write("Invalid LF Kp gain (< 0). LF Kp gains unchanged")
else:
ui._lineFollowKp.put(value)
ui._ser.write(f"LF Kp Gain Set To: {value}\r\n")
else:
ui._ser.write("No value entered. LF Kp gains unchanged.\r\n")
# Prompt and read new line follower Ki
ui._ser.write(
f"\r\nCurrent line follower (LF) Ki gain: {ui._lineFollowKi.get():.2f}\r\n"
)
ui._ser.write("Enter a new LF Ki gain value:\r\n->: ")
value = yield from multichar_input(ui._ser)
if value is not None:
if value < 0:
ui._ser.write("Invalid LF Ki gain (< 0). LF Ki gains unchanged")
else:
ui._lineFollowKi.put(value)
ui._ser.write(f"LF Ki Gain Set To: {value}\r\n")
else:
ui._ser.write("No value entered. LF Ki gains unchanged.\r\n")
# Prompt and read new LF feed forward gain
ui._ser.write(
f"\r\nCurrent LF Kff gain: {ui._lineFollowKff.get():.2f}\r\n"
)
ui._ser.write("Enter a new LF Kff gain value:\r\n->: ")
value = yield from multichar_input(ui._ser)
if value is not None:
if value < 0:
ui._ser.write("Invalid LF Kff gain (< 0). LF Kff gain unchanged")
else:
ui._lineFollowKff.put(value)
ui._ser.write(f"LF Kff Gain Set To: {value}\r\n")
else:
ui._ser.write("No value entered. LF Kff gain unchanged.\r\n")
# Show final values and return to prompt
ui._ser.write("\r\nController gains:\r\n")
ui._ser.write(f" - Motor Kp: {ui._leftMotorKp.get():.2f}\r\n")
ui._ser.write(f" - Motor Ki: {ui._leftMotorKi.get():.2f}\r\n")
ui._ser.write(f" - Line follower Kp: {ui._lineFollowKp.get():.2f}\r\n")
ui._ser.write(f" - Line follower Ki: {ui._lineFollowKi.get():.2f}\r\n")
ui._ser.write(f" - Line follower Kff: {ui._lineFollowKff.get():.2f}\r\n")
if not ui._save_gains():
ui._ser.write(f"Warning: failed to save {GAINS_FILE}.\r\n")