Source code for multichar_input

"""
Non-blocking multi-character numeric input handler for serial interfaces.

Provides a generator-based input routine for reading floating-point numbers
from a MicroPython serial port (USB_VCP or UART). Designed for use with the
cooperative multitasking scheduler: the generator yields on every loop
iteration so other tasks continue to run while waiting for user input.

Supported input:
    - Digits 0–9, optional leading ``-``, optional single ``.``
    - Backspace / rubout (``0x7F``) to delete the last character
    - Enter / Return (``\\r`` or ``\\n``) to submit
"""
from pyb import USB_VCP, UART
from task_share import BaseShare, Share

[docs] def multichar_input(ser): """ Generator that reads a floating-point number from a serial port. Accumulates characters one at a time, echoes valid input back to the terminal, and returns the parsed value when the user presses Enter. Yields ``None`` on every iteration to cooperate with the task scheduler. Args: ser: MicroPython serial object (``USB_VCP`` or ``UART``) that supports ``any()``, ``read(n)``, and ``write(s)``. Yields: None: Relinquishes the CPU on every loop so other tasks can run. Returns: float: The value entered by the user when Enter is pressed. None: If the user presses Enter with an empty buffer (no change). """ # A serial port object to use for reading characters #ser: stream = USB_VCP() # ser: stream = UART(1, 115_200) # A share or queue object where the computed number is to be placed ##out_share: BaseShare = Share('f', name="A float share") # A character buffer used to store incoming characters as they're # received by the command processor char_buf: str = "" # A set used to quickly check if a character entered by the user is # a numerical digit. digits: set(str) = set(map(str,range(10))) # A set used to quickly check if a character entered by the user is # a terminator (a carriage return or newline) term: set(str) = {"\r", "\n"} # A flag used to track whether or not the command processing is # still active. done = False # If the command is not done being processed, wait for characters # and then process them individually. while not done: # Only read and process characters if they're available. This # is to avoid blocking behavior. if ser.any(): # All commands are single characters so only one needs to # be read from the serial port. The character coming from # the serial port is of type bytes so it is cast as a # string. char_in = ser.read(1).decode() # Digits are simply appended to the incoming character # buffer and echoed back to the UI. if char_in in digits: ser.write(char_in) char_buf += char_in # Decimal points are only valid if they appear once in the # string, so the period character is ignored if one is # already in the character buffer. The decimal point is # echoed if it's added to the buffer. elif char_in == "." and "." not in char_buf: ser.write(char_in) char_buf += char_in # Dashes are used for negative values but are only valid if # they're the first character in the buffer. Valid dashes # are echoed. elif char_in == "-" and len(char_buf) == 0: ser.write(char_in) char_buf += char_in # If a "rubout" character is received, as would come from # a serial monitor like PuTTY, and there is at least one # character in the character buffer then the last character # in the buffer is removed. Echoing the validated "rubout" # character deletes the previous digit in the serial # monitor and also moves the cursor a unit to the left. elif char_in == "\x7f" and len(char_buf) > 0: ser.write("\b \b") char_buf = char_buf[:-1] # If a termination character is received it is interpreted # as the end of data entry. elif char_in in term: # If the buffer is empty then the value of the share is # left unchanged. That way the user can choose not to # enter a new value even if they've already issued the # command to change a value. if len(char_buf) == 0: ser.write("\r\n") ser.write("Value not changed\r\n") char_buf = "" done = True return None # Same as doing raise StopIteration(None) # If the character buffer is not empty the termination # character is interpreted as an end to the user input. # However if the buffer only contains a single dash or # period character the termination key is ignored. elif char_buf not in {"-", "."}: ser.write("\r\n") value = float(char_buf) ##out_share.put(value) ##ser.write(f"Value set to {value}\r\n") char_buf = "" done = True return value # Same as doing raise StopIteration(value) yield