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:
@@ -8,3 +8,7 @@ wheels/
|
|||||||
|
|
||||||
# Virtual environments
|
# Virtual environments
|
||||||
.venv
|
.venv
|
||||||
|
|
||||||
|
# Output files
|
||||||
|
*.csv
|
||||||
|
*.dlpak
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import logging
|
|||||||
from queue import Empty, Queue
|
from queue import Empty, Queue
|
||||||
import threading
|
import threading
|
||||||
from threading import Lock, Thread
|
from threading import Lock, Thread
|
||||||
from time import monotonic, monotonic_ns, sleep
|
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
|
||||||
|
from dynalab_core.buffer import ValueBuffer
|
||||||
from dynalab_core.config import CoreConfig
|
from dynalab_core.config import CoreConfig
|
||||||
from dynalab_core.constants import CORE_VERSION
|
from dynalab_core.constants import CORE_VERSION
|
||||||
from dynalab_core.errors import CoreStateMismatchError
|
from dynalab_core.errors import CoreStateMismatchError
|
||||||
@@ -33,10 +33,10 @@ class Core:
|
|||||||
"unintid"
|
"unintid"
|
||||||
)
|
)
|
||||||
self._recording = threading.Event()
|
self._recording = threading.Event()
|
||||||
|
self._recording_buffer = ValueBuffer()
|
||||||
self._core_version: VersionDescriptor = CORE_VERSION
|
self._core_version: VersionDescriptor = CORE_VERSION
|
||||||
self._stop_event: threading.Event = threading.Event()
|
self._stop_event: threading.Event = threading.Event()
|
||||||
self._core_config: CoreConfig = config
|
self._core_config: CoreConfig = config
|
||||||
# TODO: Implement live values dict
|
|
||||||
self._live_values: dict[UUID, Value] = {}
|
self._live_values: dict[UUID, Value] = {}
|
||||||
self._live_values_lock = Lock()
|
self._live_values_lock = Lock()
|
||||||
|
|
||||||
@@ -105,10 +105,12 @@ class Core:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def start_recording(self) -> None:
|
def start_recording(self) -> None:
|
||||||
|
self._recording_buffer.clear()
|
||||||
self._recording.set()
|
self._recording.set()
|
||||||
|
|
||||||
def stop_recording(self) -> None:
|
def stop_recording(self) -> None:
|
||||||
self._recording.clear()
|
self._recording.clear()
|
||||||
|
self._recording_buffer.export_csv("output.csv")
|
||||||
|
|
||||||
def get_signal_descriptor(self, signal_id: UUID) -> SignalDescriptor | None:
|
def get_signal_descriptor(self, signal_id: UUID) -> SignalDescriptor | None:
|
||||||
return self._connector_registry.get_signal_descriptor(signal_id)
|
return self._connector_registry.get_signal_descriptor(signal_id)
|
||||||
@@ -155,3 +157,7 @@ class Core:
|
|||||||
self._live_values[message.signal_id] = live_value
|
self._live_values[message.signal_id] = live_value
|
||||||
|
|
||||||
live_value.update(message.value, message.timestamp)
|
live_value.update(message.value, message.timestamp)
|
||||||
|
if self._recording.is_set():
|
||||||
|
self._recording_buffer.append(
|
||||||
|
message.timestamp, message.signal_id, message.value
|
||||||
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -19,6 +19,7 @@ config = CoreConfig(port=8765)
|
|||||||
log.info("Configured manual core on %s", config.bind_str())
|
log.info("Configured manual core on %s", config.bind_str())
|
||||||
dl_core = Core(config)
|
dl_core = Core(config)
|
||||||
dl_core.start()
|
dl_core.start()
|
||||||
|
dl_core.start_recording()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
@@ -27,4 +28,5 @@ try:
|
|||||||
log.debug(f"Core values: {values}")
|
log.debug(f"Core values: {values}")
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
log.info("Received keyboard interrupt")
|
log.info("Received keyboard interrupt")
|
||||||
|
dl_core.stop_recording()
|
||||||
dl_core.stop()
|
dl_core.stop()
|
||||||
|
|||||||
+3
-15
@@ -116,24 +116,12 @@ def value_sender_thread(
|
|||||||
|
|
||||||
next_send_time += period
|
next_send_time += period
|
||||||
|
|
||||||
|
message = ValueBatch(values=[])
|
||||||
|
for i in range(10):
|
||||||
value = ValueDescriptor(
|
value = ValueDescriptor(
|
||||||
signal_id=signal_id, value=2.0, timestamp=monotonic_ns()
|
signal_id=signal_id, value=2.0, timestamp=monotonic_ns()
|
||||||
)
|
)
|
||||||
|
message.values.append(value)
|
||||||
message = ValueBatch(
|
|
||||||
values=[
|
|
||||||
value,
|
|
||||||
value,
|
|
||||||
value,
|
|
||||||
value,
|
|
||||||
value,
|
|
||||||
value,
|
|
||||||
value,
|
|
||||||
value,
|
|
||||||
value,
|
|
||||||
value,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
future = asyncio.run_coroutine_threadsafe(
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
send_message(
|
send_message(
|
||||||
|
|||||||
Reference in New Issue
Block a user