Skip to content

Core Concepts

This page defines the essential building blocks of the Doover platform. Understanding these concepts is a prerequisite for working with pydoover, whether you are building Docker applications for edge devices or cloud processors.

Agents

An agent represents a deployable unit in the Doover platform. Every agent has a unique agent_id (a UUID) and belongs to an organisation.

There are two types of agents:

  • Device agents represent physical hardware (Doovits or other edge devices). They run Docker applications and communicate with the cloud through the device agent service.
  • Processor agents represent cloud functions that execute in response to events, schedules, or deployments.

Agents own channels, receive application deployments, and serve as the primary identity for a device or service within the platform.

from pydoover.cloud.api import Client

client = Client()
# Fetch an agent by its ID
agent = client.get_agent("a1b2c3d4-e5f6-7890-abcd-ef1234567890")

This retrieves an agent's metadata from the Control API, including its organisation, channels, and deployed applications.

Channels

A channel is a named data stream associated with an agent. Channels are the primary mechanism for data flow in Doover. Each channel has:

  • A name that identifies it within its agent (e.g., "temperature", "commands", "tag_values")
  • An ordered history of messages (the channel's log)
  • An aggregate (the channel's current state, merged from its messages)
  • A visibility setting: channels can be public (visible to the web portal and other agents) or private (only accessible via the API)

Channels are created on an agent and can be used for telemetry, commands, configuration, state synchronisation, or any other structured data flow.

# Create a channel on an agent
channel = client.create_channel(
    agent_id="a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    name="sensor_readings",
)

This creates a new channel called sensor_readings on the specified agent. Once created, you can publish messages to it and read its aggregate.

Messages

A message is an individual data record within a channel. Messages are the atoms of data in Doover. Each message has:

  • A snowflake ID that uniquely identifies it and encodes its creation timestamp (similar to Discord/Twitter snowflakes). This means messages are naturally ordered by time.
  • A data payload containing JSON-serialisable content (dictionaries, lists, numbers, strings, booleans).
  • An optional file attachment for binary data (images, firmware, logs).

Messages are append-only: once published, they form an immutable, time-ordered log within their channel.

# Publish a message to a channel
client.publish_message(
    channel_id="channel-uuid",
    data={"temperature": 23.5, "humidity": 61.2},
)

This publishes a single data point to the channel. The platform assigns a snowflake ID automatically, and the message becomes part of the channel's history.

Information Circle
Snowflake IDs

Snowflake IDs encode a millisecond-precision timestamp. You can extract the creation time from any message ID without an additional API call. pydoover provides snowflake_to_datetime() and datetime_to_snowflake() utilities for this conversion.

Aggregates

An aggregate is the current, consolidated state of a channel. While messages represent the full history, the aggregate represents "what is true right now."

Aggregates are built by merging message data. When a new message is published, its data payload is deep-merged into the channel's aggregate. This means:

  • New keys are added to the aggregate.
  • Existing keys are overwritten with the latest values.
  • The aggregate always reflects the most recent state without requiring you to replay the full message history.

Aggregates are the primary way to read real-time state from a channel. They are commonly used for:

  • Current sensor readings
  • Device configuration state
  • UI element values
  • Tag state snapshots
# Read the current aggregate for a channel
aggregate = client.get_channel_aggregate(channel_id="channel-uuid")
print(aggregate)  # {"temperature": 23.5, "humidity": 61.2, "status": "online"}

This returns the latest merged state of all messages published to the channel. It is much more efficient than fetching and replaying the full message history.

Tags

Tags are persistent key-value pairs scoped to an app_key on an agent. They provide a structured way to track application state across restarts and between the device and the cloud.

Each tag has:

  • A name (the key)
  • A value (number, boolean, or string)
  • An app_key scope (the application that owns it)

Tags are stored in a special tag_values channel aggregate on the agent. When tags are committed, they are published as a message to this channel, making tag changes part of the channel's history.

Auto-Logging Triggers

Tags support automatic logging triggers that fire when specific conditions are met:

  • Threshold crossings: A numeric tag crosses above or below a configured threshold value.
  • State transitions: A boolean or string tag changes from one value to another.

When a trigger fires, the platform automatically logs the event, enabling alerting and historical analysis without manual instrumentation.

from pydoover.tags import Number, Cross

# Define a tag with an auto-logging threshold trigger
temp_tag = Number(
    default=0.0,
    name="temperature",
    log_on=Cross(40.0),
)

This creates a numeric tag that automatically logs an event whenever the temperature crosses 40 degrees. The Cross trigger fires on both rising and falling crossings of the threshold.

For full tag documentation, see Tags Overview.

Events

Events are notifications emitted by the platform when channel data changes. They are the mechanism by which processors and real-time subscribers learn about new data.

The key event types are:

EventDescription
MessageCreateEventA new message was published to a channel
AggregateUpdateEventA channel's aggregate was updated
ChannelSyncEventA channel's full state was synchronised
DeploymentEventAn application was deployed or updated on an agent
ScheduleEventA scheduled timer fired

Events are delivered through different mechanisms depending on context:

  • Processors receive events as invocation triggers (via SNS/EventBridge). Each processor handler method corresponds to an event type.
  • WebSocket clients receive events in real-time through the Data API's WebSocket endpoint.
  • Docker applications receive events through the DeviceAgentInterface's subscription system.

For processor event handling, see Processor Events.

Applications

An application is deployed software that runs on an agent. Doover supports two application types:

Docker Applications

Docker applications run on device agents (Doovits). They are long-running, containerised processes that:

  • Execute continuously in a main loop
  • Interact with hardware through platform and Modbus interfaces
  • Communicate with the cloud through the device agent service
  • Publish telemetry, respond to commands, and manage UI elements

Docker applications are built by subclassing pydoover.docker.Application. See Docker Applications Overview for details.

Processors

Processors are cloud-side, event-driven functions that run on processor agents. They:

  • Execute in response to events (new messages, schedule triggers, deployments)
  • Run in a serverless environment (AWS Lambda)
  • Have full access to the Data and Control APIs
  • Are stateless between invocations (but can persist state through tags and channels)

Processors are built by subclassing pydoover.processor.Application. See Processors Overview for details.

Configuration

Doover uses a JSON Schema-based configuration system. Each application defines its configuration by subclassing pydoover.config.Schema and declaring typed elements (strings, numbers, booleans, enums, objects, arrays).

Configuration is:

  • Defined in code as a Schema subclass with typed elements
  • Delivered at deployment through the platform's deployment system
  • Editable at runtime through the web portal's configuration UI (auto-generated from the schema)
  • Accessible in the application via self.config in both Docker apps and processors
from pydoover.config import Schema, Number, String

class MyConfig(Schema):
    poll_interval = Number(default=30, description="Seconds between sensor polls")
    device_name = String(default="sensor-01", description="Display name for this device")

This defines a configuration schema with two fields. The platform generates a form in the web portal for editing these values, and the application receives them at startup.

For full configuration documentation, see Configuration Overview.

UI Elements

Doover provides a declarative UI system for building interactive device interfaces rendered in the web portal. UI elements are defined in application code and automatically synchronised to the portal.

There are three categories of UI elements:

  • Variables (display elements): Show data to the user. Includes NumericVariable, TextVariable, BooleanVariable, DateTimeVariable, TimestampVariable, and MultiplotVariable.
  • Interactions (input elements): Accept user input. Includes Button, Switch, Slider, Select, and Parameter.
  • Containers (layout elements): Organise other elements into groups. Includes Submodule, ApplicationContainer, RemoteComponent, and TabContainer.

UI elements can be bound to tags, allowing the display to update automatically when tag values change, and user interactions to write back to tags.

For full UI documentation, see UI Overview.

Notifications

The notification system enables alerts when specific conditions are met. It consists of two parts:

  • Endpoints define where notifications are delivered: email addresses, SMS phone numbers, web push subscriptions, or HTTP webhook URLs.
  • Subscriptions define what triggers a notification: topic-based rules that match against channel events or tag triggers.

Notifications are configured through the Control API and can be managed programmatically or through the web portal.

# Create an email notification endpoint
client.create_notification_endpoint(
    agent_id="a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    endpoint_type="email",
    address="alerts@example.com",
)

This registers an email address as a notification destination on the specified agent. You can then create subscriptions that route specific events to this endpoint.

For full notification documentation, see Notifications.