Skip to content

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

ParameterTypeDefaultDescription
namestrRequiredUnique identifier for the container
display_namestrNoneHuman-readable label
childrenlist[Element]NoneInitial child elements
status_iconstrNoneIcon representing container status
auto_add_elementsboolTrueAuto-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

ParameterTypeDefaultDescription
namestrRequiredUnique identifier
display_namestrRequiredSection header text
childrenlist[Element]NoneChild elements
statusstrNoneStatus text displayed in header
is_collapsedboolFalseInitially 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

ParameterTypeDefaultDescription
namestrRequiredApplication identifier
display_namestrNoneApplication name shown in UI
childrenlist[Element]NoneChild elements
variantstrNoneDisplay 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

ParameterTypeDefaultDescription
namestrRequiredUnique identifier
display_namestrRequiredComponent label
component_urlstrRequiredURL of the remote component
childrenlist[Element]NoneChild 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