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 |
|
|
host |
|
|
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
)
|