Skip to content

Database

Database access layer for the plant controller.

Wraps InfluxDB client functionality, providing methods to write sensor measurements and watering events, and to query historical data.

DatabaseClient

Bases: InfluxDBClient3

Extended InfluxDB client with convenience methods for plant data.

Inherits from InfluxDBClient3 and adds domain-specific read/write methods that work with Datapoint objects.

Source code in pt/controller_3/src/plant_controller/database.py
class DatabaseClient(InfluxDBClient3):
    """Extended InfluxDB client with convenience methods for plant data.

    Inherits from InfluxDBClient3 and adds domain-specific read/write
    methods that work with Datapoint objects.
    """

    def write_measurements(
        self,
        physical_unit: str,
        data: Datapoint | list[Datapoint]
    ):
        """Write one or more datapoints to the database.

        Args:
            physical_unit: Name of the unit (used for table naming).
            data: A single Datapoint or list of Datapoints to persist.
        """
        _logger.debug(f"Writing point for unit: {physical_unit}")
        try:
            if isinstance(data, Datapoint):
                point = data.to_point(physical_unit)
                _logger.debug(f"Writing single point: {point}")
                self.write(point)
            else:
                points = [dp.to_point(physical_unit) for dp in data]
                _logger.debug(f"Writing multiple points: {points}")
                self.write(points)
        except Exception as e:
            _logger.error(f"Failed to write point(s) for unit {physical_unit}: {e}", exc_info=True)
            raise e

    def read_measurements(
        self,
        physical_unit: str,
        parameter: str,
        limit: int | None = None,
        since_timestamp: datetime | None = None
    ):
        """Query historical measurements from the database.

        Args:
            physical_unit: Name of the unit to query.
            parameter: Parameter name (e.g. 'temperature', 'watering').
            limit: Maximum number of records to return (most recent first).
            since_timestamp: Only return records after this time.

        Returns:
            A pandas DataFrame of matching records, ordered by time descending.
        """
        query = (
            f'SELECT * FROM ' + Datapoint.format_for_table_name(
                physical_unit,
                parameter
            )
            + (f" WHERE time > '{since_timestamp.isoformat()}'" if since_timestamp else '')
            + f' ORDER BY time DESC'
            + (f' LIMIT {limit}' if limit else '')
        )
        _logger.debug(f'Executing query: {query}')
        return self.query(
            query
        ).to_pandas()

write_measurements(physical_unit, data)

Write one or more datapoints to the database.

Parameters:

Name Type Description Default
physical_unit str

Name of the unit (used for table naming).

required
data Datapoint | list[Datapoint]

A single Datapoint or list of Datapoints to persist.

required
Source code in pt/controller_3/src/plant_controller/database.py
def write_measurements(
    self,
    physical_unit: str,
    data: Datapoint | list[Datapoint]
):
    """Write one or more datapoints to the database.

    Args:
        physical_unit: Name of the unit (used for table naming).
        data: A single Datapoint or list of Datapoints to persist.
    """
    _logger.debug(f"Writing point for unit: {physical_unit}")
    try:
        if isinstance(data, Datapoint):
            point = data.to_point(physical_unit)
            _logger.debug(f"Writing single point: {point}")
            self.write(point)
        else:
            points = [dp.to_point(physical_unit) for dp in data]
            _logger.debug(f"Writing multiple points: {points}")
            self.write(points)
    except Exception as e:
        _logger.error(f"Failed to write point(s) for unit {physical_unit}: {e}", exc_info=True)
        raise e

read_measurements(physical_unit, parameter, limit=None, since_timestamp=None)

Query historical measurements from the database.

Parameters:

Name Type Description Default
physical_unit str

Name of the unit to query.

required
parameter str

Parameter name (e.g. 'temperature', 'watering').

required
limit int | None

Maximum number of records to return (most recent first).

None
since_timestamp datetime | None

Only return records after this time.

None

Returns:

Type Description

A pandas DataFrame of matching records, ordered by time descending.

Source code in pt/controller_3/src/plant_controller/database.py
def read_measurements(
    self,
    physical_unit: str,
    parameter: str,
    limit: int | None = None,
    since_timestamp: datetime | None = None
):
    """Query historical measurements from the database.

    Args:
        physical_unit: Name of the unit to query.
        parameter: Parameter name (e.g. 'temperature', 'watering').
        limit: Maximum number of records to return (most recent first).
        since_timestamp: Only return records after this time.

    Returns:
        A pandas DataFrame of matching records, ordered by time descending.
    """
    query = (
        f'SELECT * FROM ' + Datapoint.format_for_table_name(
            physical_unit,
            parameter
        )
        + (f" WHERE time > '{since_timestamp.isoformat()}'" if since_timestamp else '')
        + f' ORDER BY time DESC'
        + (f' LIMIT {limit}' if limit else '')
    )
    _logger.debug(f'Executing query: {query}')
    return self.query(
        query
    ).to_pandas()

Database

Database connection configuration and client factory.

Attributes:

Name Type Description
token

Authentication token for InfluxDB.

name

Database (bucket) name.

host

InfluxDB host URL.

Source code in pt/controller_3/src/plant_controller/database.py
class Database:
    """Database connection configuration and client factory.

    Attributes:
        token: Authentication token for InfluxDB.
        name: Database (bucket) name.
        host: InfluxDB host URL.
    """

    def __init__(
        self,
        token: str,
        name: str = 'plant-controller',
        host: str = 'http://127.0.0.1:8181',
    ):
        self.token=token
        self.name=name
        self.host=host

    def exists(self) -> bool:
        """Check if the database exists (currently always returns True)."""
        return True

    def initialize(self):
        """Initialize the database (no-op, reserved for future use)."""
        pass

    def spawn_client(self) -> DatabaseClient:
        """Create and return a new DatabaseClient connected to this database."""
        return DatabaseClient(
            host=self.host,
            database=self.name,
            token=self.token
        )

exists()

Check if the database exists (currently always returns True).

Source code in pt/controller_3/src/plant_controller/database.py
def exists(self) -> bool:
    """Check if the database exists (currently always returns True)."""
    return True

initialize()

Initialize the database (no-op, reserved for future use).

Source code in pt/controller_3/src/plant_controller/database.py
def initialize(self):
    """Initialize the database (no-op, reserved for future use)."""
    pass

spawn_client()

Create and return a new DatabaseClient connected to this database.

Source code in pt/controller_3/src/plant_controller/database.py
def spawn_client(self) -> DatabaseClient:
    """Create and return a new DatabaseClient connected to this database."""
    return DatabaseClient(
        host=self.host,
        database=self.name,
        token=self.token
    )