Interactive Elements
Interactive elements accept user input from the Doover portal and trigger actions on the device. All interactive elements inherit from Interaction, which provides the base value management and handler routing.
Interaction Base
Every interactive element shares these capabilities from the Interaction base class:
- value property -- reads the current value from the
UICommandsManager - set(value, max_age, log_update) -- updates the element's value in the portal
- handler(ctx, payload) -- called when the user interacts with the element; default implementation calls
set(payload) - requires_confirm -- when set to
Trueor aConfirmDialog, shows a confirmation dialog before executing - global_interaction -- marks the interaction as device-global rather than app-scoped
Button
A clickable button that fires a handler when pressed.
from pydoover.ui import Button
restart_btn = Button("Restart System")
# With disabled state and label
fetch_btn = Button(
"Fetch Data",
disabled=False,
label_string="Click to fetch latest data",
)
| Parameter | Description |
|---|---|
display_name | Label shown on the button |
disabled | Whether the button appears greyed out |
label_string | Additional descriptive text shown near the button |
Switch
A toggle switch for on/off control. Inherits directly from Interaction with no additional parameters.
from pydoover.ui import Switch
pump_toggle = Switch("Pump Control")
The value is typically a boolean. When the user toggles the switch, the handler receives the new state.
Select
A dropdown menu with a fixed set of options.
from pydoover.ui import Select, Option
mode_select = Select(
"Operating Mode",
options=[
Option("Automatic"),
Option("Manual"),
Option("Standby"),
],
)
Each Option has a display_name shown in the dropdown. The name (the internal key) is auto-generated by sanitising the display name. When the user selects an option, the handler receives the option's name as the payload.
| Parameter | Description |
|---|---|
display_name | Label for the dropdown |
options | List of Option instances |
Slider
A range input with configurable bounds, step size, and visual options.
from pydoover.ui import Slider
brightness = Slider(
"Brightness",
min_val=0,
max_val=100,
step_size=1,
dual_slider=False,
inverted=False,
)
| Parameter | Description |
|---|---|
display_name | Label for the slider |
min_val | Minimum value (default: 0) |
max_val | Maximum value (default: 100) |
step_size | Increment size (default: 0.1) |
dual_slider | Whether the slider has two handles for range selection (default: True) |
inverted | Whether the slider direction is inverted (default: True) |
colours | Colour string for the slider track |
A monitoring dashboard uses dual sliders for alarm thresholds (min/max range) and a single slider for a device setting:
from pydoover import ui
alarms = ui.Submodule(
"Alarms",
children=[
ui.Slider(
"Humidity Alarm",
units="%",
min_val=30,
max_val=95,
step_size=0.1,
dual_slider=True,
inverted=False,
icon="fa-regular fa-bell",
show_activity=False,
default=[30, 95],
name="humidity_alarm",
),
ui.Slider(
"Temperature Alarm",
units="°C",
min_val=-10,
max_val=50,
step_size=0.1,
dual_slider=True,
inverted=False,
icon="fa-regular fa-bell",
show_activity=False,
default=[0, 30],
name="temp_alarm",
),
],
)
sleep_slider = ui.Slider(
"Sleep Time",
units="hrs",
min_val=0.25,
max_val=12,
step_size=0.25,
dual_slider=False,
icon="fa-regular fa-bed",
show_activity=True,
default=6,
name="sleep_time",
)
When dual_slider=True, the default value is a list of two numbers [min, max], and the handler receives a tuple. When dual_slider=False, it's a single number.
WarningIndicator
Displays a warning state that the user can optionally dismiss.
from pydoover.ui import WarningIndicator
alarm = WarningIndicator("High Temperature Alarm", can_cancel=True)
| Parameter | Description |
|---|---|
display_name | Warning message text |
can_cancel | Whether the user can dismiss the warning (default: True) |
FloatInput
A numeric input field with optional min/max bounds.
from pydoover.ui import FloatInput
setpoint = FloatInput(
"Temperature Setpoint",
min_val=0,
max_val=100,
default=25,
)
| Parameter | Description |
|---|---|
display_name | Label for the input |
min_val | Minimum accepted value |
max_val | Maximum accepted value |
default | Default value when no user value exists |
TextInput
A text input field, optionally rendered as a larger text area.
from pydoover.ui import TextInput
device_name = TextInput("Device Name")
notes = TextInput("Notes", is_text_area=True)
| Parameter | Description |
|---|---|
display_name | Label for the input |
is_text_area | When True, renders as a multi-line text area (default: False) |
DatetimeInput
A date and time picker. Internally stores values as epoch seconds in UTC.
from pydoover.ui import DatetimeInput
schedule_start = DatetimeInput("Schedule Start", include_time=True)
date_only = DatetimeInput("Calibration Date", include_time=False)
| Parameter | Description |
|---|---|
display_name | Label for the picker |
include_time | Whether to show the time picker in addition to date (default: False) |
TimeInput
A time-only picker (no date component). This is a convenience subclass of DatetimeInput with include_time=True.
from pydoover.ui import TimeInput
daily_report_time = TimeInput("Daily Report Time")
Confirmation Dialogs
Any interactive element can require user confirmation before executing — essential for safety-critical controls like engine starts, pump shutdowns, or parameter changes that affect running equipment. Set requires_confirm to True for a default dialog, or pass a ConfirmDialog for customisation.
from pydoover.ui import Button, ConfirmDialog, Colour
shutdown_btn = Button(
"Shutdown System",
requires_confirm=ConfirmDialog(
title="Confirm Shutdown",
subtitle="The system will go offline",
warning_reason="This will stop all running processes",
colour=Colour.red,
icon="alert-triangle",
),
)
See Styling for ConfirmDialog details.
Handling Interactions
When a user interacts with an element in the portal, the event flows to your application through the UICommandsManager. You handle events using the @handler() decorator on your application class.
The @handler() Decorator
Mark an async method as a handler for a specific interaction. The method receives an InteractionContext and the payload (the new value).
from pydoover.docker import Application
from pydoover.ui import handler, InteractionContext, Button
class MyUI(UI):
restart_btn = Button("Restart System")
setpoint = FloatInput("Setpoint", min_val=0, max_val=100)
class MyApp(Application):
ui_cls = MyUI
@handler("restart_btn")
async def on_restart(self, ctx: InteractionContext, payload):
await self.tags.status.set("restarting")
self.request_shutdown()
@handler("setpoint", auto_update=False)
async def on_setpoint(self, ctx: InteractionContext, payload):
if payload < 10:
return # Reject values below 10
await ctx.set_value(payload) # Manually update
You can also pass the interaction element directly instead of its name:
@handler(MyUI.restart_btn)
async def on_restart(self, ctx, payload):
pass
Handler Parameters
| Parameter | Description |
|---|---|
interaction | The interaction name (string), Interaction instance, or regex pattern to match |
parser | Optional function to transform the payload before the handler receives it |
auto_update | When True (default), automatically updates the element's value with the payload after the handler returns successfully |
InteractionContext
The handler's ctx argument is an InteractionContext that provides:
| Property/Method | Description |
|---|---|
ctx.element | The Interaction instance that was triggered |
ctx.interaction | Alias for ctx.element |
ctx.method | The interaction name string |
ctx.message | The raw channel message that triggered the handler |
ctx.set_value(value, max_age, log_update) | Update the interaction's value in the portal |
Module-Qualified Form
The handler decorator is also available as ui.handler() when importing the ui module directly. This form works in both Docker applications and cloud processors.
from pydoover import ui
from pydoover.processor import Application
class MyProcessor(Application):
@ui.handler("sleep_time", parser=float)
async def on_sleep_time_change(self, ctx, payload: float):
await self._apply_sleep_time(max(0.25, payload))
@ui.handler("selected_crop", parser=str)
async def on_crop_change(self, ctx, payload: str):
await self.tags.app_display_name.set(f"Monitor - {payload}")
An engine controller uses @ui.handler() in a Docker application to send modbus commands when operators press buttons. The parser=int argument converts the slider payload to an integer before the handler receives it:
from pydoover import ui
from pydoover.docker import Application
class EngineControllerApp(Application):
@ui.handler("start_pump")
async def on_start_pump(self, ctx, value):
if not self.config.enable_remote_control.value:
return
if await self.write_start():
await self.create_message(
"activity_logs",
{"message": f"{self.app_display_name}: remote START sent"},
)
@ui.handler("target_rpm", parser=int)
async def on_target_rpm(self, ctx, value: int):
if not self.config.enable_remote_control.value:
return
rpm_min = int(self.config.target_rpm_min.value)
rpm_max = int(self.config.target_rpm_max.value)
rpm = max(rpm_min, min(rpm_max, value))
await self.write_target_rpm(rpm)
A cloud processor uses @ui.handler() with parser to handle selection changes and slider adjustments from the portal:
from pydoover import ui
@ui.handler("sleep_time", parser=float)
async def on_sleep_time_change(self, ctx, payload: float):
await self._publish_downlink_config(sleep_hours=max(0.25, payload))
@ui.handler("operating_mode", parser=str)
async def on_mode_change(self, ctx, payload: str):
await self.tags.app_display_name.set(f"Monitor - {payload}")
for elem in self.config.sensor_labels.elements:
s_id = elem.id.value
await self.recalculate_derived_values(s_id, payload)
Default Handler
If no @handler() is registered for an interaction, the base Interaction.handler() method is called, which simply sets the element's value to the received payload. This means simple interactions (like a Select that just stores the selected option) work without any custom handler code.
Next Steps
- UI Overview -- introduction to the UI system
- Display Elements -- read-only variables and charts
- Containers -- structural grouping
- UI Commands Manager -- deeper look at the command routing system