diff --git a/src/cli/__init__.py b/src/cli/__init__.py index adf2709..3d46786 100644 --- a/src/cli/__init__.py +++ b/src/cli/__init__.py @@ -6,13 +6,12 @@ import numpy.typing as npt import typer from .. import visualise -from ..collection import ingest from ..config import config from ..processing.aoa import AoA from ..processing.preprocess import Preprocessor -from . import file +from . import file, globals -app = typer.Typer() +app = typer.Typer(callback=globals.main) logger = logging.getLogger(__name__) @@ -27,6 +26,9 @@ def antennas() -> None: """ from ..utils import antenna_order + if not globals.is_live: + raise ValueError("This command only works with live data") + antenna_order.main() @@ -48,7 +50,8 @@ def heatmap() -> None: if not webapp_queue.full(): webapp_queue.put(aoa) - ingest.start_processing(callback) + globals.csi_producer(csi_callback=callback) + logger.info("Finished processing CSI data") app.add_typer(file.app, name="file", help="Commands for working with CSI files") diff --git a/src/cli/file.py b/src/cli/file.py index f72277e..4bcea7c 100644 --- a/src/cli/file.py +++ b/src/cli/file.py @@ -7,7 +7,8 @@ import numpy as np import numpy.typing as npt import typer -from ..collection import ingest +from . import globals + app = typer.Typer() logger = logging.getLogger(__name__) @@ -24,17 +25,4 @@ def capture(output_path: Path) -> None: logger.info(f"Got final CSI data with shape {antenna_data.shape}") file.create_dataset(datetime.now().isoformat(), data=antenna_data) - ingest.start_processing(callback) - - -@app.command() -def replay(input_path: Path) -> None: - """Replay CSI data from a file""" - logger.info(f"Replaying data from {input_path}") - - start_time = datetime.now() - - with h5py.File(input_path, "r") as file: - for key in file: - logger.info(f"Sending data from {key}") - ingest.send_data(file[key][:]) + globals.csi_producer(csi_callback=callback) diff --git a/src/cli/globals.py b/src/cli/globals.py new file mode 100644 index 0000000..cf5f0b9 --- /dev/null +++ b/src/cli/globals.py @@ -0,0 +1,18 @@ +from functools import partial +from pathlib import Path + +from .. import collection +from ..collection import file, ingest + +csi_producer: collection.CSIProducer = collection.noop +is_live = True + + +def main(from_file: Path | None = None) -> None: + global csi_producer, is_live + if from_file: + csi_producer = partial(file.start_processing, file_path=from_file) + is_live = False + else: + csi_producer = ingest.start_processing + is_live = True diff --git a/src/collection/__init__.py b/src/collection/__init__.py new file mode 100644 index 0000000..93a1b8c --- /dev/null +++ b/src/collection/__init__.py @@ -0,0 +1,9 @@ +from . import file, ingest +from .protocols import CSICallback, CSIProducer + + +def noop(csi_callback: CSICallback | None = None) -> None: + del csi_callback + + +__all__ = ["file", "ingest", "noop", "CSIProducer", "CSICallback"] diff --git a/src/collection/file.py b/src/collection/file.py new file mode 100644 index 0000000..f83b9ca --- /dev/null +++ b/src/collection/file.py @@ -0,0 +1,39 @@ +import logging +import time +from datetime import datetime, timedelta +from pathlib import Path + +import h5py + +from . import protocols + +logger = logging.getLogger(__name__) + + +def start_processing( + file_path: Path, + csi_callback: protocols.CSICallback | None = None, +) -> None: + logger.info(f"Replaying CSI data from {file_path}") + with h5py.File(file_path, "r") as file: + try: + for key in file: + datetime.fromisoformat(key) + except ValueError as e: + logger.exception( + "The file provided was not generated using this software", e + ) + + start_time = datetime.fromisoformat(list(file.keys())[0]) + target_offset = datetime.now() - start_time + for key in file: + logger.debug(f"Sending data from {key} at {datetime.now().isoformat()}") + csi_callback(file[key][:]) + curr_time_virtual = datetime.fromisoformat(key) + new_offset = datetime.now() - curr_time_virtual + logger.debug(f"New offset {new_offset}, target is {target_offset}") + if new_offset > target_offset + timedelta(seconds=1): + logger.warning( + f"Data is {new_offset - target_offset} behind, lagging behind..." + ) + time.sleep(max(0, (target_offset - new_offset).total_seconds())) diff --git a/src/collection/ingest.py b/src/collection/ingest.py index bee7a89..1d1ab0f 100644 --- a/src/collection/ingest.py +++ b/src/collection/ingest.py @@ -9,13 +9,12 @@ from datetime import datetime from typing import Callable, NamedTuple import numpy as np -import numpy.typing as npt from ..config import config from .csi_frame import CSI +from .protocols import CSICallback Host = tuple[str, int] -CSICallback = Callable[[npt.NDArray[np.complex128]], None] class FeitHost: diff --git a/src/collection/protocols.py b/src/collection/protocols.py new file mode 100644 index 0000000..d58505a --- /dev/null +++ b/src/collection/protocols.py @@ -0,0 +1,10 @@ +from typing import Callable, Protocol + +import numpy as np +import numpy.typing as npt + +CSICallback = Callable[[npt.NDArray[np.complex128]], None] + + +class CSIProducer(Protocol): + def __call__(self, csi_callback: CSICallback | None = None) -> None: ... diff --git a/src/utils/antenna_order.py b/src/utils/antenna_order.py index dc7e5bf..17673b7 100644 --- a/src/utils/antenna_order.py +++ b/src/utils/antenna_order.py @@ -28,12 +28,12 @@ def callback(antenna_data: dict[ingest.Host, ingest.CSI]) -> None: ) for host, csi in antenna_data.items(): antenna_average[(host, 0)] = ( - antenna_average.get((host, 0), csi.header.rssi1) * 0.9 - + csi.header.rssi1 * 0.1 + antenna_average.get((host, 0), csi.header.rssi1) * 0.99 + + csi.header.rssi1 * 0.01 ) antenna_average[(host, 1)] = ( - antenna_average.get((host, 1), csi.header.rssi2) * 0.9 - + csi.header.rssi2 * 0.1 + antenna_average.get((host, 1), csi.header.rssi2) * 0.99 + + csi.header.rssi2 * 0.01 ) if csi.header.rssi1 > antenna_average[(host, 0)] + RSSI_THRESHOLD: unplugged.add((host, 0))