Operator Interface with OpenHTF
OpenHTF can pause a test to interact with the operator through its user_input plug. When the test runs on a TofuPilot station, these prompts render in the station kiosk: an optional image, your message, and either a confirm button or a text field. This guide covers that full surface with examples, and ends with how it looks on the station.
What the Operator Sees
A prompt appears on one screen with:
- An optional image, above the message
- The message text
- A Continue button, or a text field, depending on the call
The operator never opens a terminal or a separate viewer.
Prerequisites
- Python 3.10+
- OpenHTF installed (
pip install openhtf)
The UserInput Plug
A single call drives the prompt. Attach the UserInput plug to a phase and call prompt().
from openhtf.plugs import user_inputprompts.prompt( message, # text shown to the operator text_input=False, # False = confirm button, True = text field timeout_s=None, # optional, raises PromptUnansweredError on timeout image_url=None, # optional image shown inline in the prompt)It returns the operator's text (an empty string when text_input=False).
Message and Continue Button
import openhtf as htffrom openhtf.plugs import user_input@htf.plug(prompts=user_input.UserInput)def power_cycle(test, prompts): prompts.prompt("Power-cycle the unit, wait for the LED, then click Continue.")Text Input
@htf.plug(prompts=user_input.UserInput)def scan_serial(test, prompts): serial = prompts.prompt("Scan the serial number:", text_input=True) test.dut_id = serialYes / No Decisions
OpenHTF has no two-button Yes/No widget. The pattern is always the same: ask for a typed answer, then branch on it, usually returning PhaseResult.CONTINUE to go on or PhaseResult.STOP to halt the test. The example below gates the test on an LED check.
@htf.plug(prompts=user_input.UserInput)def led_check(test, prompts): answer = prompts.prompt("Does the LED blink green? Type y or n:", text_input=True) return htf.PhaseResult.CONTINUE if answer.strip().lower() == "y" \ else htf.PhaseResult.STOPThe same shape fits many operator decisions, for example: confirming a connector is fully seated before applying power, judging a visual pass or fail on a finish or a label, or deciding whether a unit goes to rework after an inspection.
Show an Image in the Prompt
Pass image_url and the image renders inline, above the message, live during the run. It works with a confirm button and with a text field. The URL is anything an HTML image tag accepts.
Hosted image
@htf.plug(prompts=user_input.UserInput)def connector_check(test, prompts): prompts.prompt( "Connect the cable as shown, then click Continue.", image_url="http://localhost:8080/reference/connector.png", )Local image as a data URI (no server needed)
On a station this is usually the simplest: read a local file and inline it as a base64 data URI, so there is nothing to host.
import base64def data_uri(path, mime="image/png"): with open(path, "rb") as f: return f"data:{mime};base64," + base64.b64encode(f.read()).decode("ascii")@htf.plug(prompts=user_input.UserInput)def visual_inspection(test, prompts): reference = data_uri("/opt/station/reference/board_top.jpg", "image/jpeg") answer = prompts.prompt( "Does the board match the reference? Type y or n:", text_input=True, image_url=reference, ) return htf.PhaseResult.CONTINUE if answer.strip().lower() == "y" \ else htf.PhaseResult.STOPImage captured during the test
@htf.plug(prompts=user_input.UserInput)def confirm_capture(test, prompts): path = "/tmp/capture.png" camera.grab_frame(path) # your capture code prompts.prompt( "Is the captured image in focus and centered?", text_input=True, image_url=data_uri(path, "image/png"), )Notes: use image/png or image/jpeg; the URL must load from the browser running the kiosk, so a data URI is the safest on a station. image_url shows the image live; to also keep it in the run record, attach it with test.attach_from_file(path).
Live Status Text
OpenHTF has no self-updating console line for the operator. To surface progress during a phase, use test.logger: each call emits a log record live, streamed to whatever operator UI is showing the run (OpenHTF's own web GUI, or the TofuPilot kiosk) and kept in the test record. Use it for progress, then a short prompt once the step is done.
@htf.plug(prompts=user_input.UserInput)def discharge(test, prompts): test.logger.info("Waiting for capacitor to discharge...") # live log entry wait_for_discharge() # your code prompts.prompt("Capacitor discharged. Click Continue.")What the Operator Sees in TofuPilot
These prompts render in the TofuPilot station kiosk, the operator UI the CLI serves locally and opens in a browser. Enable the kiosk on the station, or force it for a single run with tofupilot run --kiosk. When a phase calls prompt(), the kiosk shows the image, the message, and the Continue button or text field on one screen; the operator responds and the run continues. To watch from the dashboard, open the station's operator view at <your-tofupilot-url>/<org>/operator/<station-id>.
If you need richer inputs than these OpenHTF prompts offer, such as dropdowns, checklists, sliders, switches, or a live progress bar, you can add them with a TofuPilot framework procedure that declares UI components.