Components
Viseron is architectured around the concept of components and domains. Components can provide any type of functionality and can optionally setup one or more domains. This guide will walk you through the process of creating a new component.
A component name of fancy_component will be used as an example throughout this guide.
Component Architecture
Components in Viseron can have two optional functions:
| Function | Purpose | Required |
|---|---|---|
setup() | Component-level initialization (create shared resources, initialize connections) | No |
setup_domains() | Register domains (cameras, detectors, etc.) | No |
At least one of these functions must be present. The loading sequence is:
setup()is called first (if it exists) - must returnTrueon successsetup_domains()is called after successful setup (if it exists)
It is very important that setup_domains() only registers domains and nothing else, since this method can be called multiple times during hot-reloading of configuration changes.
What is a Domain?
Domains are interfaces that define types of functionality in Viseron. Components implement domains to provide specific capabilities. For example:
- The
cameradomain is implemented by bothffmpegandgstreamercomponents - The
object_detectordomain is implemented bydarknet,edgetpu,hailo, and others
This plug-and-play architecture allows for loose coupling between components and makes it easier for users to swap implementations.
For detailed information on implementing domains, see the Domains documentation.
Stateless vs Stateful Components
Stateless components only need setup_domains(). These components don't initialize any shared resources and simply register domains. Examples: ffmpeg, gstreamer, nvr.
Stateful components need both setup() and setup_domains(). These components initialize shared resources (neural networks, connections, etc.) in setup() before registering domains. Examples: darknet, edgetpu, hailo.
Creating a new Component
To create a new component called fancy_component you need to create the directory viseron/components/fancy_component. The directory name should be in lowercase.
Stateless Component (setup_domains only)
For simple components that only register domains without any shared state:
"""The fancy_component component."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from viseron import Viseron
def setup_domains(vis: Viseron, config: dict[str, Any]) -> None:
"""Set up domains for the fancy_component component."""
# Register your domains here
pass
Stateful Component (setup + setup_domains)
For components that need to initialize shared resources before registering domains:
"""The fancy_component component."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from viseron import Viseron
from .const import COMPONENT
def setup(vis: Viseron, config: dict[str, Any]) -> bool:
"""Set up the fancy_component component.
Initialize shared resources here. This runs before setup_domains().
"""
# Initialize shared resources (neural networks, connections, etc.)
vis.data[COMPONENT] = MySharedResource(config)
return True
def setup_domains(vis: Viseron, config: dict[str, Any]) -> None:
"""Set up domains for the fancy_component component."""
# Register your domains here
pass
The setup function will be called when Viseron is started. The function should return True if the setup was successful, otherwise False.
You should also create a const.py file where you define constants for your component.
"""Constants for the fancy_component component."""
COMPONENT = "fancy_component"
From here you can start adding your component code. The component will be loaded by adding the component name to the config.yaml:
fancy_component:
Setting up Domains
If your component needs to set up domains, use the setup_domains() function. This function is called after setup() (if present) and is responsible for registering all domains.
This function should call setup_domain() for each domain instance your component provides.
def setup_domains(vis: Viseron, config: dict[str, Any]) -> None:
"""Set up fancy_component object detector domains."""
config = config[COMPONENT]
for camera_identifier in config[CONFIG_OBJECT_DETECTOR][CONFIG_CAMERAS].keys():
setup_domain(
vis,
COMPONENT,
CONFIG_OBJECT_DETECTOR,
config,
identifier=camera_identifier,
require_domains=[
RequireDomain(
domain="camera",
identifier=camera_identifier,
)
],
)
It is very important that setup_domains() only registers domains and nothing else, since this method can be called multiple times during hot-reloading of configuration changes.
This separation enables hot-reloading of configuration changes. When a user updates their config:
setup_domains()can be called again to register new domains without re-initializing expensive shared resources- The domain registry prevents duplicate registration, so only new domains are added
- Existing domains can be reloaded individually with updated configuration
Configuration schema
Viseron uses voluptuous for configuration validation.
To add configuration options to your component you need to define a CONFIG_SCHEMA constant in the __init__.py file.
"""The fancy_component component."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
import voluptuous as vol
from viseron.components.fancy_component.const import (
CONFIG_COOL_OPTION,
DEFAULT_COOL_OPTION,
DESC_COOL_OPTION,
)
from viseron.components.storage.const import COMPONENT, DESC_COMPONENT
if TYPE_CHECKING:
from viseron import Viseron
CONFIG_SCHEMA = vol.Schema(
{
vol.Required(COMPONENT, description=DESC_COMPONENT): vol.Schema(
{
vol.Optional(
CONFIG_COOL_OPTION,
default=DEFAULT_COOL_OPTION,
description=DESC_COOL_OPTION,
): str,
}
)
},
extra=vol.ALLOW_EXTRA,
)
def setup(vis: Viseron, config: dict[str, Any]) -> bool:
"""Set up the fancy_component component."""
# Your setup code for the fancy_component here
return True
"""Constants for the fancy_component component."""
COMPONENT = "fancy_component"
DESC_COMPONENT = "The fancy_component component."
CONFIG_COOL_OPTION = "cool_option"
DESC_COOL_OPTION = "A cool option"
DEFAULT_COOL_OPTION = "cool_value"
The description is used to generate documentation for the component. More information can be found in the documentation section.
Deprecating a config option
If you need to deprecate a config option, you can use the Deprecated validator.
import voluptuous as vol
from viseron.components.fancy_component.const import (
CONFIG_COOL_OPTION,
DEFAULT_COOL_OPTION,
DESC_COOL_OPTION,
)
from viseron.components.storage.const import COMPONENT, DESC_COMPONENT
if TYPE_CHECKING:
from viseron import Viseron
CONFIG_SCHEMA = vol.Schema(
{
vol.Required(COMPONENT, description=DESC_COMPONENT): vol.Schema(
{
Deprecated(
CONFIG_FILENAME_PATTERN,
description=DESC_FILENAME_PATTERN_THUMBNAIL,
message=DEPRECATED_FILENAME_PATTERN_THUMBNAIL,
warning=WARNING_FILENAME_PATTERN_THUMBNAIL,
): str,
}
)
},
extra=vol.ALLOW_EXTRA,
)
The Deprecated validator has the following parameters:
config: The config option to deprecate.description: Displayed in the generated documentation.message: Displayed in the generated documentation.warning: Displayed in the logs.
Component documentation
For information on how to generate documentation for your component, see the documentation section.