Added ValueBuffer implementation and simple recording

ValueBuffer is now the default buffer object for storing data for
recording and processing purposes, a simple recording implementation has
been done, core.py example starts recording as soon as the core has
started then on exit it saves the output as a csv file through the
ValueBuffer export_csv method
This commit is contained in:
2026-08-06 12:57:59 +01:00
parent 1690032f5b
commit ffcea13c0b
5 changed files with 80 additions and 20 deletions
+4
View File
@@ -8,3 +8,7 @@ wheels/
# Virtual environments
.venv
# Output files
*.csv
*.dlpak
+8 -2
View File
@@ -6,11 +6,11 @@ import logging
from queue import Empty, Queue
import threading
from threading import Lock, Thread
from time import monotonic, monotonic_ns, sleep
from typing import Literal
from uuid import UUID
from dynalab_core.buffer import ValueBuffer
from dynalab_core.config import CoreConfig
from dynalab_core.constants import CORE_VERSION
from dynalab_core.errors import CoreStateMismatchError
@@ -33,10 +33,10 @@ class Core:
"unintid"
)
self._recording = threading.Event()
self._recording_buffer = ValueBuffer()
self._core_version: VersionDescriptor = CORE_VERSION
self._stop_event: threading.Event = threading.Event()
self._core_config: CoreConfig = config
# TODO: Implement live values dict
self._live_values: dict[UUID, Value] = {}
self._live_values_lock = Lock()
@@ -105,10 +105,12 @@ class Core:
)
def start_recording(self) -> None:
self._recording_buffer.clear()
self._recording.set()
def stop_recording(self) -> None:
self._recording.clear()
self._recording_buffer.export_csv("output.csv")
def get_signal_descriptor(self, signal_id: UUID) -> SignalDescriptor | None:
return self._connector_registry.get_signal_descriptor(signal_id)
@@ -155,3 +157,7 @@ class Core:
self._live_values[message.signal_id] = live_value
live_value.update(message.value, message.timestamp)
if self._recording.is_set():
self._recording_buffer.append(
message.timestamp, message.signal_id, message.value
)
+60
View File
@@ -0,0 +1,60 @@
# Copyright (C) 2026 Hector van der Aa <hector@h3cx.dev>
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
# SPDX-License-Identifier: GPL-3.0-or-later
from collections import defaultdict
import csv
from pathlib import Path
from threading import Lock
from uuid import UUID
class ValueBuffer:
def __init__(self) -> None:
self._samples: list[tuple[int, UUID, float]] = []
self._lock = Lock()
def append(self, timestamp: int, uuid: UUID, value: float) -> None:
with self._lock:
self._samples.append((timestamp, uuid, value))
def clear(self) -> None:
with self._lock:
self._samples.clear()
def __len__(self) -> int:
with self._lock:
return len(self._samples)
def export_csv(self, path: str | Path) -> None:
with self._lock:
samples = list(self._samples)
rows: dict[int, dict[UUID, float]] = defaultdict(dict)
signal_ids: set[UUID] = set()
for timestamp_ns, signal_id, value in samples:
rows[timestamp_ns][signal_id] = value
signal_ids.add(signal_id)
ordered_signal_ids = sorted(signal_ids, key=str)
with Path(path).open(mode="w", newline="", encoding="utf-8") as file:
writer = csv.writer(file)
writer.writerow(
["timestamp_ns", *(str(signal_id) for signal_id in ordered_signal_ids)]
)
for timestamp_ns in sorted(rows):
timestamp_values = rows[timestamp_ns]
writer.writerow(
[
timestamp_ns,
*(
timestamp_values.get(signal_id, "")
for signal_id in ordered_signal_ids
),
]
)
+2
View File
@@ -19,6 +19,7 @@ config = CoreConfig(port=8765)
log.info("Configured manual core on %s", config.bind_str())
dl_core = Core(config)
dl_core.start()
dl_core.start_recording()
try:
while True:
@@ -27,4 +28,5 @@ try:
log.debug(f"Core values: {values}")
except KeyboardInterrupt:
log.info("Received keyboard interrupt")
dl_core.stop_recording()
dl_core.stop()
+6 -18
View File
@@ -116,24 +116,12 @@ def value_sender_thread(
next_send_time += period
value = ValueDescriptor(
signal_id=signal_id, value=2.0, timestamp=monotonic_ns()
)
message = ValueBatch(
values=[
value,
value,
value,
value,
value,
value,
value,
value,
value,
value,
]
)
message = ValueBatch(values=[])
for i in range(10):
value = ValueDescriptor(
signal_id=signal_id, value=2.0, timestamp=monotonic_ns()
)
message.values.append(value)
future = asyncio.run_coroutine_threadsafe(
send_message(