Skip to content

Reading data

DVDataset

DVDataset is a frozen Pydantic model. It is the output of every read_dataset() call.

Field Type Description
production int Realtime AC production in W
consumption int Realtime consumption in W
grid_feed int Grid exchange in W — positive = feeding, negative = consuming
limitation_nb_percent float \| None Active Netzbetreiber curtailment in %
limitation_nb_w float \| None Active Netzbetreiber curtailment in W
limitation_dv_percent float \| None Active Direktvermarkter curtailment in %
limitation_dv_w float \| None Active Direktvermarkter curtailment in W

None means the driver either does not support that field or the device did not return a meaningful value. There are no field-level constraints — out-of-range hardware values are stored as-is so reads never raise a ValidationError on unexpected device behaviour.


read_dataset()

Reads all metrics in one call:

ds = iface.read_dataset()

ds.production            # 5000
ds.consumption           # 1200
ds.grid_feed             # 3800
ds.limitation_nb_percent # 100.0 or None
ds.limitation_nb_w       # None
ds.limitation_dv_percent # 80.0  or None
ds.limitation_dv_w       # None

Modbus drivers override read_dataset() to batch all register reads into a single TCP request. Calling read_dataset() is therefore faster and more reliable than calling individual read methods back to back.


Individual read methods

Use these when you only need a single value:

iface.read_production()             # int, W
iface.read_consumption()            # int, W
iface.read_gridfeed()               # int, W
iface.read_limitation_nb_percent()  # float | None
iface.read_limitation_nb_w()        # float | None
iface.read_limitation_dv_percent()  # float | None
iface.read_limitation_dv_w()        # float | None

Each call is a separate TCP round-trip. Prefer read_dataset() when you need several values at once.


DVReadResult

read_dataset_result() returns a DVReadResult wrapping the dataset with timing and identity metadata:

result = iface.read_dataset_result()

result.interface   # 'solarlog'
result.host        # '192.168.1.100'
result.elapsed_s   # 0.043  (rounded to 3 decimal places)
result.read_at     # datetime in UTC, set at the start of the read
result.dataset     # DVDataset

to_dict()

to_dict() flattens the dataset fields inline with the result metadata — ready for database insertion:

result.to_dict()
# {
#     'interface': 'solarlog',
#     'host': '192.168.1.100',
#     'elapsed_s': 0.043,
#     'read_at': '2026-05-21T08:12:33.421000+00:00',
#     'production': 5000,
#     'consumption': 1200,
#     'grid_feed': 3800,
#     'limitation_nb_percent': 100.0,
#     'limitation_nb_w': None,
#     'limitation_dv_percent': 80.0,
#     'limitation_dv_w': None,
# }

age_s and is_stale()

Track how old a cached result is:

result = iface.read_dataset_result()

# ... some time passes ...

print(result.age_s)              # seconds since the read was initiated
if result.is_stale(max_age_s=300):
    result = iface.read_dataset_result()

diff()

Compare two DVDataset objects to detect what changed between readings:

prev = iface.read_dataset()
# ... wait ...
curr = iface.read_dataset()

changed = prev.diff(curr)
# {'production': (5000, 4800), 'grid_feed': (3800, 3600)}

if changed:
    logger.info('plant state changed: %s', changed)

diff() returns a {field: (old_value, new_value)} dict. Empty dict means nothing changed.