Containers
Containers are UI elements that group other elements together. They provide logical organization and visual structure to your device interface.
Import
from pydoover.ui import (
Container,
Submodule,
Application,
RemoteComponent,
)
Container (Base Class)
The Container class is the base class for all container elements. It provides methods for managing child elements.
Constructor
Container(
name: str,
display_name: str = None,
children: list[Element] = None,
status_icon: str = None,
auto_add_elements: bool = True,
**kwargs
)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | Required | Unique identifier for the container |
display_name | str | None | Human-readable label |
children | list[Element] | None | Initial child elements |
status_icon | str | None | Icon representing container status |
auto_add_elements | bool | True | Auto-register class attributes as children |
Properties
children
Returns a list of all child elements.
for child in my_container.children:
print(child.name)
Methods
add_children()
Adds one or more child elements to the container.
from pydoover.ui import Container, NumericVariable, TextVariable
container = Container("sensors", "Sensors")
# Add single element
container.add_children(
NumericVariable("temp", "Temperature", curr_val=25.0)
)
# Add multiple elements
container.add_children(
NumericVariable("humidity", "Humidity", curr_val=60.0),
TextVariable("status", "Status", curr_val="OK")
)
remove_children()
Removes child elements from the container.
container.remove_children(element_to_remove)
Note: Best practice is to set hidden=True on elements instead of removing them.
set_children()
Replaces all children with a new list.
container.set_children([new_element1, new_element2])
clear_children()
Removes all child elements.
container.clear_children()
get_element()
Retrieves a child element by name (searches recursively).
temp = container.get_element("temperature")
get_all_elements()
Returns all elements in the container hierarchy.
# Get all elements all_elements = container.get_all_elements() # Filter by type from pydoover.ui import Variable variables_only = container.get_all_elements(type_filter=Variable)
Submodule
Submodules group related UI elements into collapsible sections. They are the primary way to organize your UI into logical components.
Constructor
Submodule(
name: str,
display_name: str,
children: list[Element] = None,
status: str = None,
is_collapsed: bool = False,
**kwargs
)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | Required | Unique identifier |
display_name | str | Required | Section header text |
children | list[Element] | None | Child elements |
status | str | None | Status text displayed in header |
is_collapsed | bool | False | Initially collapsed state |
Example
from pydoover.ui import (
UIManager,
Submodule,
NumericVariable,
TextVariable,
Action,
Colour,
)
ui = UIManager(app_key="my_device", client=my_client)
# Create sensor readings submodule
sensor_submodule = Submodule(
name="sensors",
display_name="Sensor Readings",
status="All sensors OK",
is_collapsed=False,
children=[
NumericVariable("temperature", "Temperature (C)", curr_val=25.5),
NumericVariable("humidity", "Humidity (%)", curr_val=60),
NumericVariable("pressure", "Pressure (hPa)", curr_val=1013),
]
)
# Create controls submodule
controls_submodule = Submodule(
name="controls",
display_name="Device Controls",
status="Ready",
is_collapsed=True, # Start collapsed
children=[
Action("restart", "Restart", colour=Colour.red),
Action("calibrate", "Calibrate", colour=Colour.blue),
]
)
# Add submodules to UI
ui.add_children(sensor_submodule, controls_submodule)
Class-Based Submodule
You can create submodules as classes with predefined elements:
from pydoover.ui import Submodule, NumericVariable, Action, action, Colour
class SensorModule(Submodule):
# Class attributes are auto-added as children
temperature = NumericVariable("temperature", "Temperature", curr_val=0)
humidity = NumericVariable("humidity", "Humidity", curr_val=0)
def __init__(self):
super().__init__(
name="sensors",
display_name="Sensors",
status="Initializing"
)
@action("refresh", "Refresh Sensors", colour=Colour.blue)
def refresh(self, new_value):
print("Refreshing sensors...")
def update_readings(self, temp, humid):
self.temperature.current_value = temp
self.humidity.current_value = humid
self.status = "Online"
# Usage
sensor_module = SensorModule()
ui.add_children(sensor_module)
# Update values
sensor_module.update_readings(25.5, 60)
Real-World Example: Battery Module
This example from a Doover application template shows a Submodule grouping related battery monitoring elements:
from pydoover import ui
class SampleUI:
def __init__(self):
# Create the battery submodule
self.battery = ui.Submodule("battery", "Battery Module")
# Battery voltage with colored ranges
self.battery_voltage = ui.NumericVariable(
"battery_voltage",
"Battery Voltage",
precision=2,
ranges=[
ui.Range("Low", 0, 10, ui.Colour.red),
ui.Range("Normal", 10, 20, ui.Colour.green),
ui.Range("High", 20, 30, ui.Colour.blue),
]
)
# Alert threshold parameter
self.battery_low_voltage_alert = ui.NumericParameter(
"battery_low_voltage_alert",
"Low Voltage Alert"
)
# Charge mode dropdown
self.battery_charge_mode = ui.StateCommand(
"battery_charge_mode",
"Charge Mode",
user_options=[
ui.Option("charge", "Charging"),
ui.Option("discharge", "Discharging"),
ui.Option("idle", "Idle"),
]
)
# Add all battery elements to the submodule
self.battery.add_children(
self.battery_voltage,
self.battery_low_voltage_alert,
self.battery_charge_mode
)
Source: app-template
Nested Submodules
Submodules can contain other submodules for hierarchical organization:
# Inner submodules
temp_sensors = Submodule(
name="temp_sensors",
display_name="Temperature Sensors",
children=[
NumericVariable("temp_1", "Sensor 1", curr_val=25.0),
NumericVariable("temp_2", "Sensor 2", curr_val=26.0),
]
)
humidity_sensors = Submodule(
name="humidity_sensors",
display_name="Humidity Sensors",
children=[
NumericVariable("humid_1", "Sensor 1", curr_val=55),
NumericVariable("humid_2", "Sensor 2", curr_val=60),
]
)
# Outer submodule containing nested ones
all_sensors = Submodule(
name="all_sensors",
display_name="All Sensors",
children=[temp_sensors, humidity_sensors]
)
ui.add_children(all_sensors)
Application
The Application container represents the top-level UI container for your device application. It is typically managed internally by the UIManager.
Constructor
Application(
name: str,
display_name: str = None,
children: list[Element] = None,
variant: str = None,
**kwargs
)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | Required | Application identifier |
display_name | str | None | Application name shown in UI |
children | list[Element] | None | Child elements |
variant | str | None | Display variant ("stacked" or "submodule") |
Application Variants
The variant controls how your application is displayed:
from pydoover.ui import UIManager, ApplicationVariant ui = UIManager(app_key="my_app", client=my_client) # Stacked: Elements are displayed directly ui.set_variant(ApplicationVariant.stacked) # Submodule: Application is wrapped in a collapsible section ui.set_variant(ApplicationVariant.submodule)
ApplicationVariant
from pydoover.ui import ApplicationVariant # Available variants ApplicationVariant.stacked # Elements stacked without wrapper ApplicationVariant.submodule # Wrapped in a submodule container
RemoteComponent
Remote components allow embedding external UI components hosted at a URL.
Constructor
RemoteComponent(
name: str,
display_name: str,
component_url: str,
children: list[Element] = None,
**kwargs
)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | Required | Unique identifier |
display_name | str | Required | Component label |
component_url | str | Required | URL of the remote component |
children | list[Element] | None | Child elements (optional) |
Example
from pydoover.ui import RemoteComponent
custom_chart = RemoteComponent(
name="custom_chart",
display_name="Analytics Dashboard",
component_url="https://my-components.example.com/chart"
)
ui.add_children(custom_chart)
Working with UIManager
The UIManager uses an internal Application container. You typically interact with it through the manager's methods:
from pydoover.ui import UIManager, Submodule, NumericVariable
ui = UIManager(app_key="device_app", client=my_client)
# Set application properties
ui.set_display_name("My IoT Device")
ui.set_status_icon("ok") # or "warning", "error"
ui.set_position(10)
# Add elements directly to the application
ui.add_children(
Submodule("sensors", "Sensors", children=[
NumericVariable("temp", "Temperature", curr_val=25.0)
])
)
# Get elements from anywhere in the hierarchy
temp = ui.get_element("temp")
Element Positioning
Elements are automatically positioned based on the order they are added. You can override this:
sensor1 = NumericVariable("sensor1", "Sensor 1", curr_val=10, position=1)
sensor2 = NumericVariable("sensor2", "Sensor 2", curr_val=20, position=2)
sensor3 = NumericVariable("sensor3", "Sensor 3", curr_val=30, position=3)
# sensor1 will appear first, then sensor2, then sensor3
submodule = Submodule("sensors", "Sensors", children=[sensor3, sensor1, sensor2])
Complete Example
from pydoover.ui import (
UIManager,
Submodule,
Container,
NumericVariable,
TextVariable,
BooleanVariable,
Action,
StateCommand,
Slider,
Option,
Colour,
Range,
ApplicationVariant,
)
class DeviceUI:
def __init__(self, client):
self.ui = UIManager(app_key="smart_device", client=client)
self.setup_ui()
self.ui.start_comms()
def setup_ui(self):
# Set application properties
self.ui.set_display_name("Smart Device Controller")
self.ui.set_variant(ApplicationVariant.stacked)
# Create status submodule
status_module = Submodule(
name="status",
display_name="Device Status",
status="Online",
is_collapsed=False,
children=[
BooleanVariable("power", "Power", curr_val=True),
TextVariable("mode", "Current Mode", curr_val="Auto"),
NumericVariable(
"uptime",
"Uptime (hours)",
curr_val=0,
precision=1
),
]
)
# Create sensor readings submodule
sensors_module = Submodule(
name="sensors",
display_name="Sensor Readings",
children=[
NumericVariable(
"temperature",
"Temperature (C)",
curr_val=25.0,
precision=1,
ranges=[
Range("Cold", 0, 18, Colour.blue),
Range("Normal", 18, 28, Colour.green),
Range("Hot", 28, 50, Colour.red),
]
),
NumericVariable(
"humidity",
"Humidity (%)",
curr_val=55,
precision=0
),
]
)
# Create controls submodule
controls_module = Submodule(
name="controls",
display_name="Controls",
is_collapsed=True,
children=[
StateCommand(
"mode_select",
"Operating Mode",
user_options=[
Option("auto", "Automatic"),
Option("manual", "Manual"),
Option("eco", "Eco Mode"),
]
),
Slider(
"setpoint",
"Temperature Setpoint",
min_val=15,
max_val=30,
step_size=0.5,
dual_slider=False
),
Action(
"restart",
"Restart Device",
colour=Colour.red,
requires_confirm=True
),
]
)
# Add all submodules to UI
self.ui.add_children(
status_module,
sensors_module,
controls_module
)
def update_sensors(self, temp, humidity):
self.ui.update_variable("temperature", temp)
self.ui.update_variable("humidity", humidity)
async def run(self):
while True:
# Update sensor values
self.update_sensors(read_temp(), read_humidity())
# Handle communication
await self.ui.handle_comms_async()
await asyncio.sleep(1)
# Usage
device = DeviceUI(my_client)
asyncio.run(device.run())
See Also
- UIManager - Managing the UI
- Display Elements - Variables for displaying data
- Interactive Elements - User input elements
- Styling - Colors, ranges, and options