From 1e23ae063e5d0e0f6417707439f4d5735f5deb78 Mon Sep 17 00:00:00 2001 From: Christos Falas Date: Thu, 23 Jan 2025 21:48:47 +0000 Subject: [PATCH] feat: add cli util for finding correct antenna order --- src/cli.py | 21 +++++++++++--- src/collection/ingest.py | 34 +++++++++++++---------- src/utils/antenna_order.py | 56 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 19 deletions(-) create mode 100644 src/utils/antenna_order.py diff --git a/src/cli.py b/src/cli.py index 40457cb..c0063d1 100644 --- a/src/cli.py +++ b/src/cli.py @@ -1,3 +1,4 @@ +import logging import multiprocessing as mp import numpy as np @@ -10,6 +11,21 @@ from .processing.aoa import AoA from .processing.preprocess import Preprocessor app = typer.Typer() +logger = logging.getLogger(__name__) + + +@app.command() +def antennas() -> None: + """Utility to help determine the order in which antennas are plugged in + + Once the script is running, unplug and replug antennas from left to right, to get + the correct order. Every time an antenna is unplugged and replugged, the script will + print the antenna identifier. When you are done, press Ctrl+C to stop the script and + get the final order. + """ + from .utils import antenna_order + + antenna_order.main() @app.command() @@ -22,6 +38,7 @@ def heatmap() -> None: webapp.start() def callback(antenna_data: npt.NDArray[np.complex128]) -> None: + logger.info(f"Got final CSI data with shape {antenna_data.shape}") processed = preprocessor.preprocess(antenna_data) # visualise.add_data(all_data, processed) aoa.update(processed) @@ -29,7 +46,3 @@ def heatmap() -> None: webapp_queue.put(aoa) ingest.start_processing(callback) - - -if __name__ == "__main__": - app() diff --git a/src/collection/ingest.py b/src/collection/ingest.py index 016ed0f..cad463f 100644 --- a/src/collection/ingest.py +++ b/src/collection/ingest.py @@ -130,14 +130,6 @@ class CSIProcessor: with self.pending_data_lock: self.pending_data[host] = (datetime.now(), data) - # Useful for figuring out the correct antenna order - RSSI values will - # decrease when the specific antenna is disconnected - rssis = [ - (ip, csi.header.rssi1, csi.header.rssi2) - for ip, (_, csi) in sorted(self.pending_data.items()) - ] - self.logger.debug("Antenna RSSI values: {}".format(rssis)) - def is_ready(self) -> bool: for host in config.RECEIVE_HOSTS: if ( @@ -147,8 +139,16 @@ class CSIProcessor: return False return True - def process_data(self, callback: CSICallback) -> None: + def process_data( + self, + callback: CSICallback | None = None, + pre_merge_callback: Callable[[dict[Host, CSI]], None] | None = None, + ) -> None: self.last_processed = datetime.now() + if pre_merge_callback is not None: + pre_merge_callback( + {host: data[1] for host, data in self.pending_data.items()} + ) antenna_data = [ np.expand_dims(self.pending_data[ip][1].matrix[:, antenna], axis=2) for ip, antenna in config.ANTENNA_ORDER @@ -156,20 +156,21 @@ class CSIProcessor: # We have data from all servers all_data = np.concat(antenna_data, axis=1) - self.logger.info(f"Got final CSI data with shape {all_data.shape}") - callback(all_data) + if callback: + callback(all_data) def process_forever( self, - callback: CSICallback, + callback: CSICallback | None = None, + pre_merge_callback: Callable[[dict[Host, CSI]], None] | None = None, ) -> NoReturn: while True: for ip, queue in self.connections.items(): while not queue.empty(): self.add_data(ip, queue.get()) if self.is_ready(): - self.process_data(callback) + self.process_data(callback, pre_merge_callback=pre_merge_callback) else: self.logger.debug("Not all data is ready") time.sleep(0.0005) @@ -181,7 +182,10 @@ class Receiver(NamedTuple): queue: "mp.Queue[CSI]" -def start_processing(csi_callback: CSICallback) -> None: +def start_processing( + csi_callback: CSICallback | None = None, + pre_merge_callback: Callable[[dict[Host, CSI]], None] | None = None, +) -> None: receivers = [ Receiver(ip, FeitReceiver(ip), mp.Queue(config.SAMPLE_RATE)) for ip in config.RECEIVE_HOSTS @@ -200,7 +204,7 @@ def start_processing(csi_callback: CSICallback) -> None: proc.start() processing_thread = mp.Process( - target=processor.process_forever, args=(csi_callback,) + target=processor.process_forever, args=(csi_callback, pre_merge_callback) ) processing_thread.start() try: diff --git a/src/utils/antenna_order.py b/src/utils/antenna_order.py new file mode 100644 index 0000000..dc7e5bf --- /dev/null +++ b/src/utils/antenna_order.py @@ -0,0 +1,56 @@ +import logging + +from ..collection import ingest + +logger = logging.getLogger(__name__) + +AntennaIdentifier = tuple[ingest.Host, int] +order: list[AntennaIdentifier] = [] +prev_unplugged: set[AntennaIdentifier] = set() +antenna_average: dict[AntennaIdentifier, float] = {} + +RSSI_THRESHOLD = 8 + + +def callback(antenna_data: dict[ingest.Host, ingest.CSI]) -> None: + global prev_unplugged + global antenna_average + global order + unplugged: set[tuple[ingest.Host, int]] = set() + logger.debug( + "Antenna RSSI: " + + str( + { + host: (csi.header.rssi1, csi.header.rssi2) + for host, csi in antenna_data.items() + } + ) + ) + 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[(host, 1)] = ( + antenna_average.get((host, 1), csi.header.rssi2) * 0.9 + + csi.header.rssi2 * 0.1 + ) + if csi.header.rssi1 > antenna_average[(host, 0)] + RSSI_THRESHOLD: + unplugged.add((host, 0)) + if csi.header.rssi2 > antenna_average[(host, 1)] + RSSI_THRESHOLD: + unplugged.add((host, 1)) + if prev_unplugged != unplugged: + if len(prev_unplugged) > len(unplugged): + logger.info(f"Antenna plugged in: {prev_unplugged - unplugged}") + else: + logger.info(f"Antenna unplugged: {unplugged - prev_unplugged}") + diff = list(unplugged - prev_unplugged) + if len(diff) == 1 and diff[0] not in order: + host, antenna = diff.pop() + order.append((host, antenna)) + logger.info(f"Current order: {order}") + prev_unplugged = unplugged + + +def main() -> None: + ingest.start_processing(pre_merge_callback=callback)