Applications Overview
The pydoover Docker application framework provides a structured way to build IoT applications that run on Doover-powered devices. Applications built with this framework can communicate with the Doover cloud, interact with hardware I/O, and communicate with Modbus devices.
Architecture
A pydoover application consists of several key components that work together:
- Application Class: The main entry point that orchestrates your application logic
- DeviceAgentInterface: Handles communication with the Doover cloud via the Device Agent
- PlatformInterface: Provides access to hardware I/O (digital inputs/outputs, analog inputs/outputs)
- ModbusInterface: Enables communication with Modbus devices over serial or TCP
Core Concepts
Application Lifecycle
Every pydoover application follows a consistent lifecycle:
- Initialization: The application is created with a configuration schema
- Setup: The
setup()method is called once to initialize resources - Main Loop: The
main_loop()method runs continuously at a configurable interval - Shutdown: Cleanup occurs when the application terminates
Configuration
Applications use a configuration schema to define their settings. This allows for runtime configuration through the Doover cloud:
from pydoover.config import Schema, Integer, Boolean, String
class MyAppConfig(Schema):
def __init__(self):
self.poll_interval = Integer("Poll Interval", default=5, min_val=1)
self.enable_logging = Boolean("Enable Logging", default=True)
self.device_name = String("Device Name", default="sensor-1")
Tags
Tags are key-value pairs that provide a simple way to share state between applications and with the cloud:
# Set a tag for the current application
self.set_tag("temperature", 25.5)
# Get a tag value
temp = self.get_tag("temperature")
# Get a tag from another application
other_value = self.get_tag("status", app_key="other-app-key")
# Set a global tag (shared across all applications)
self.set_global_tag("system_status", "online")
Creating Applications with doover-cli
The recommended way to create new applications is using the doover-cli tool:
# Install doover-cli uv tool install doover-cli # Create a new application doover app create
The interactive wizard will guide you through setting up:
- Application name and description
- Container registry configuration
- Organization settings
This creates a complete project structure including Docker configuration, simulators for local testing, and deployment scripts. See the Doover CLI documentation for the full development workflow.
Getting Started
Minimal Application
Here is a minimal pydoover application:
from pydoover.docker import Application, run_app
from pydoover.config import Schema
class MinimalApp(Application):
def setup(self):
print("Application starting...")
def main_loop(self):
print("Running main loop...")
if __name__ == "__main__":
run_app(MinimalApp(config=Schema()))
Application with Configuration
A more complete example with configuration:
from pydoover.docker import Application, run_app
from pydoover.config import Schema, Integer, Boolean
class SensorConfig(Schema):
def __init__(self):
self.num_sensors = Integer("Sensor Count", default=4, min_val=1)
self.output_enabled = Boolean("Output Enabled", default=True)
class SensorApp(Application):
config: SensorConfig # Type hint for IDE autocomplete
def setup(self):
self.set_tag("app_ready", True)
print(f"Monitoring {self.config.num_sensors.value} sensors")
def main_loop(self):
for i in range(self.config.num_sensors.value):
status = self.get_di(i)
print(f"Sensor {i}: {'active' if status else 'inactive'}")
if __name__ == "__main__":
run_app(SensorApp(config=SensorConfig()))
Application Attributes
The Application class provides access to several important attributes:
| Attribute | Type | Description |
|---|---|---|
config | Schema | The configuration schema for the application |
device_agent | DeviceAgentInterface | Interface for cloud communication |
platform_iface | PlatformInterface | Interface for hardware I/O |
modbus_iface | ModbusInterface | Interface for Modbus communication |
ui_manager | UIManager | Manager for user interface elements |
app_key | str | Unique identifier for this application |
loop_target_period | float | Target interval for main loop execution (default: 1 second) |
Loop Timing
You can control how fast the main loop runs by setting loop_target_period:
class FastApp(Application):
def setup(self):
self.loop_target_period = 0.5 # Run every 500ms
def main_loop(self):
# This runs every 500ms
pass
The framework will automatically adjust timing to maintain the target interval, and will log warnings if the loop consistently runs slower than the target.
Async Support
Both setup() and main_loop() can be synchronous or asynchronous:
class AsyncApp(Application):
async def setup(self):
await self.some_async_initialization()
async def main_loop(self):
result = await self.fetch_data_async()
await self.set_tag_async("result", result)
Shutdown Handling
Applications can implement custom shutdown logic:
class SafeApp(Application):
async def on_shutdown_at(self, dt):
"""Called when a shutdown is scheduled."""
print(f"Shutdown scheduled for {dt}")
# Perform pre-shutdown tasks
async def check_can_shutdown(self) -> bool:
"""Called to check if the application can safely shutdown."""
# Check if it's safe to shutdown
if self.get_do(0): # If output is still high
return False # Don't shutdown yet
return True
Next Steps
- Application Class Reference - Detailed API documentation
- DeviceAgentInterface - Cloud communication
- PlatformInterface - Hardware I/O
- ModbusInterface - Modbus communication
- Examples - Complete application examples