format: re-format with ruff and isort

This commit is contained in:
Christos Falas 2025-01-23 11:13:17 +00:00
parent 5a481ae13b
commit 8e1a849b7c
No known key found for this signature in database
6 changed files with 65 additions and 57 deletions

View File

@ -1,6 +1,12 @@
[tool.ruff] [tool.ruff]
line-length = 88 line-length = 88
[tool.ruff.lint]
extend-select = ["I", "E", "W", "F", "B", "Q", "ANN"]
[tool.ruff.lint.pycodestyle]
max-doc-length = 88
[tool.pyright] [tool.pyright]
typeCheckingMode = "strict" typeCheckingMode = "strict"
reportMissingTypeStubs = "warning" reportMissingTypeStubs = "warning"

View File

@ -1,20 +1,19 @@
import typer
import multiprocessing as mp import multiprocessing as mp
from .collection import ingest
from .processing.preprocess import Preprocessor
from .processing.aoa import AoA
from . import config
from . import visualise
import numpy as np import numpy as np
import numpy.typing as npt import numpy.typing as npt
import typer
from . import config, visualise
from .collection import ingest
from .processing.aoa import AoA
from .processing.preprocess import Preprocessor
app = typer.Typer() app = typer.Typer()
@app.command() @app.command()
def heatmap(): def heatmap() -> None:
preprocessor = Preprocessor() preprocessor = Preprocessor()
aoa = AoA() aoa = AoA()
webapp_queue: "mp.Queue[AoA]" = mp.Queue(config.SAMPLE_RATE) webapp_queue: "mp.Queue[AoA]" = mp.Queue(config.SAMPLE_RATE)
@ -22,7 +21,7 @@ def heatmap():
webapp = mp.Process(target=visualise.start, args=(webapp_queue,)) webapp = mp.Process(target=visualise.start, args=(webapp_queue,))
webapp.start() webapp.start()
def callback(antenna_data: npt.NDArray[np.complex128]): def callback(antenna_data: npt.NDArray[np.complex128]) -> None:
processed = preprocessor.preprocess(antenna_data) processed = preprocessor.preprocess(antenna_data)
# visualise.add_data(all_data, processed) # visualise.add_data(all_data, processed)
aoa.update(processed) aoa.update(processed)

View File

@ -1,8 +1,8 @@
import struct import struct
import numpy as np import numpy as np
import numpy.typing as npt import numpy.typing as npt
RATE_MCS_MOD_TYPE_POS = 8 RATE_MCS_MOD_TYPE_POS = 8
RATE_MCS_MOD_TYPE_MSK = 0x7 << RATE_MCS_MOD_TYPE_POS RATE_MCS_MOD_TYPE_MSK = 0x7 << RATE_MCS_MOD_TYPE_POS
RATE_MCS_CCK_MSK = 0 << RATE_MCS_MOD_TYPE_POS RATE_MCS_CCK_MSK = 0 << RATE_MCS_MOD_TYPE_POS
@ -37,7 +37,7 @@ RATE_MCS_BEAMF_MSK = 1 << RATE_MCS_BEAMF_POS
class CSIHeader: class CSIHeader:
def __init__(self, data: bytes): def __init__(self, data: bytes) -> None:
self.csi_size = struct.unpack("I", data[0:4])[0] self.csi_size = struct.unpack("I", data[0:4])[0]
self.ftm_clock = struct.unpack("I", data[8:12])[0] self.ftm_clock = struct.unpack("I", data[8:12])[0]
self.num_rx = data[46] self.num_rx = data[46]
@ -93,7 +93,7 @@ class CSIHeader:
class CSI: class CSI:
@staticmethod @staticmethod
def parseCsiData(data: bytes, header: CSIHeader): def parseCsiData(data: bytes, header: CSIHeader) -> npt.NDArray[np.complex128]:
csi_matrix: npt.NDArray[np.complex128] = np.zeros( csi_matrix: npt.NDArray[np.complex128] = np.zeros(
( (
header.num_subcarriers, header.num_subcarriers,
@ -113,7 +113,7 @@ class CSI:
return csi_matrix return csi_matrix
def __init__(self, data: bytes): def __init__(self, data: bytes) -> None:
self.header = CSIHeader(data[:272]) self.header = CSIHeader(data[:272])
self.matrix = self.parseCsiData( self.matrix = self.parseCsiData(
data[272 : 272 + self.header.csi_size], self.header data[272 : 272 + self.header.csi_size], self.header

View File

@ -1,26 +1,25 @@
import logging import logging
import time
import socket
import multiprocessing as mp import multiprocessing as mp
import threading import socket
import subprocess
import struct import struct
from typing import Callable import subprocess
import threading
import time
from datetime import datetime from datetime import datetime
from typing import Callable, NamedTuple, NoReturn
import numpy as np import numpy as np
import numpy.typing as npt import numpy.typing as npt
from typing import NamedTuple
from .csi_frame import CSI
from .. import config from .. import config
from .csi_frame import CSI
Host = tuple[str, int] Host = tuple[str, int]
CSICallback = Callable[[npt.NDArray[np.complex128]], None] CSICallback = Callable[[npt.NDArray[np.complex128]], None]
class FeitHost: class FeitHost:
def __init__(self, host: Host, command: str): def __init__(self, host: Host, command: str) -> None:
self.command = command self.command = command
self.host = host self.host = host
self.logger = logging.getLogger( self.logger = logging.getLogger(
@ -29,7 +28,7 @@ class FeitHost:
self.checker = threading.Thread(target=self.check_continuous) self.checker = threading.Thread(target=self.check_continuous)
self.checker.start() self.checker.start()
def check_connection(self): def check_connection(self) -> bool:
feitcsi_status = subprocess.run( feitcsi_status = subprocess.run(
f"ssh root@{self.host[0]} pgrep feitcsi", f"ssh root@{self.host[0]} pgrep feitcsi",
check=False, check=False,
@ -38,18 +37,20 @@ class FeitHost:
) )
return feitcsi_status.returncode == 0 return feitcsi_status.returncode == 0
def connect(self): def connect(self) -> None:
self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.server.connect(self.host) self.server.connect(self.host)
self.server.send(b"stop\n") self.server.send(b"stop\n")
self.server.send(self.command.encode()) self.server.send(self.command.encode())
self.logger.info(f"Connected to {self.host}") self.logger.info(f"Connected to {self.host}")
def check_continuous(self): def check_continuous(self) -> NoReturn:
""" """
Repeatedly check if the FeitCSI service is running Repeatedly check if the FeitCSI service is running
Because FeitCSI is using TCP, we will get no information if the service stops, or the computer is not reachable. In order to make debugging easier, this checks and logs continuously if the service is running. Because FeitCSI is using TCP, we will get no information if the service stops,
or the computer is not reachable. In order to make debugging easier, this checks
and logs continuously if the service is running.
""" """
last_status = False last_status = False
while True: while True:
@ -64,7 +65,7 @@ class FeitHost:
class FeitTransmitter(FeitHost): class FeitTransmitter(FeitHost):
def __init__(self): def __init__(self) -> None:
command = ( command = (
f"feitcsi --frequency {config.CENTRAL_FREQUENCY_MHZ} " f"feitcsi --frequency {config.CENTRAL_FREQUENCY_MHZ} "
f"--channel-width {config.CHANNEL_WIDTH} " f"--channel-width {config.CHANNEL_WIDTH} "
@ -76,7 +77,7 @@ class FeitTransmitter(FeitHost):
class FeitReceiver(FeitHost): class FeitReceiver(FeitHost):
def __init__(self, host: Host): def __init__(self, host: Host) -> None:
command = ( command = (
f"feitcsi --frequency {config.CENTRAL_FREQUENCY_MHZ} " f"feitcsi --frequency {config.CENTRAL_FREQUENCY_MHZ} "
f"--channel-width {config.CHANNEL_WIDTH} " f"--channel-width {config.CHANNEL_WIDTH} "
@ -85,7 +86,7 @@ class FeitReceiver(FeitHost):
) )
super().__init__(host, command) super().__init__(host, command)
def listen(self, queue: "mp.Queue[CSI]"): def listen(self, queue: "mp.Queue[CSI]") -> NoReturn:
prev_time = datetime.now() prev_time = datetime.now()
self.logger.info("Listening for CSI data") self.logger.info("Listening for CSI data")
while True: while True:
@ -110,14 +111,14 @@ class CSIProcessor:
def __init__( def __init__(
self, self,
receiver_connections: dict[Host, "mp.Queue[CSI]"], receiver_connections: dict[Host, "mp.Queue[CSI]"],
): ) -> None:
self.pending_data: dict[Host, tuple[datetime, CSI]] = {} self.pending_data: dict[Host, tuple[datetime, CSI]] = {}
self.pending_data_lock = mp.Lock() self.pending_data_lock = mp.Lock()
self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}") self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
self.last_processed = datetime.now() self.last_processed = datetime.now()
self.connections = receiver_connections self.connections = receiver_connections
def add_data(self, host: Host, data: CSI): def add_data(self, host: Host, data: CSI) -> None:
if ( if (
host in self.pending_data host in self.pending_data
and self.last_processed < self.pending_data[host][0] and self.last_processed < self.pending_data[host][0]
@ -129,15 +130,15 @@ class CSIProcessor:
with self.pending_data_lock: with self.pending_data_lock:
self.pending_data[host] = (datetime.now(), data) self.pending_data[host] = (datetime.now(), data)
# Useful for figuring out the correct antenna order - RSSI values will decrease # Useful for figuring out the correct antenna order - RSSI values will
# when the specific antenna is disconnected # decrease when the specific antenna is disconnected
rssis = [ rssis = [
(ip, csi.header.rssi1, csi.header.rssi2) (ip, csi.header.rssi1, csi.header.rssi2)
for ip, (_, csi) in sorted(self.pending_data.items()) for ip, (_, csi) in sorted(self.pending_data.items())
] ]
self.logger.debug("Antenna RSSI values: {}".format(rssis)) self.logger.debug("Antenna RSSI values: {}".format(rssis))
def is_ready(self): def is_ready(self) -> bool:
for host in config.RECEIVE_HOSTS: for host in config.RECEIVE_HOSTS:
if ( if (
host not in self.pending_data host not in self.pending_data
@ -146,7 +147,7 @@ class CSIProcessor:
return False return False
return True return True
def process_data(self, callback: CSICallback): def process_data(self, callback: CSICallback) -> None:
self.last_processed = datetime.now() self.last_processed = datetime.now()
antenna_data = [ antenna_data = [
np.expand_dims(self.pending_data[ip][1].matrix[:, antenna], axis=2) np.expand_dims(self.pending_data[ip][1].matrix[:, antenna], axis=2)
@ -162,7 +163,7 @@ class CSIProcessor:
def process_forever( def process_forever(
self, self,
callback: CSICallback, callback: CSICallback,
): ) -> NoReturn:
while True: while True:
for ip, queue in self.connections.items(): for ip, queue in self.connections.items():
while not queue.empty(): while not queue.empty():
@ -180,7 +181,7 @@ class Receiver(NamedTuple):
queue: "mp.Queue[CSI]" queue: "mp.Queue[CSI]"
def start_processing(csi_callback: CSICallback): def start_processing(csi_callback: CSICallback) -> None:
receivers = [ receivers = [
Receiver(ip, FeitReceiver(ip), mp.Queue(config.SAMPLE_RATE)) Receiver(ip, FeitReceiver(ip), mp.Queue(config.SAMPLE_RATE))
for ip in config.RECEIVE_HOSTS for ip in config.RECEIVE_HOSTS

View File

@ -1,22 +1,23 @@
import numpy as np import logging
import numpy.typing as npt
from .. import config
from datetime import datetime from datetime import datetime
import logging import numpy as np
import numpy.typing as npt
from .. import config
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class AoA: class AoA:
def __init__(self): def __init__(self) -> None:
self.historical_autocorr = np.array([]) self.historical_autocorr = np.array([])
self.N_subcarriers = -1 self.N_subcarriers = -1
self.N_rx = -1 self.N_rx = -1
self.timestamp = datetime.now() self.timestamp = datetime.now()
pass pass
def smooth(self, data: npt.NDArray[np.complex128]): def smooth(self, data: npt.NDArray[np.complex128]) -> npt.NDArray[np.complex128]:
assert len(data.shape) == 3 assert len(data.shape) == 3
M = data.shape[0] # Number of subcarriers M = data.shape[0] # Number of subcarriers
@ -44,7 +45,7 @@ class AoA:
return H_sm return H_sm
def update(self, data: npt.NDArray[np.complex128]): def update(self, data: npt.NDArray[np.complex128]) -> None:
self.timestamp = datetime.now() self.timestamp = datetime.now()
H_sm = self.smooth(data) H_sm = self.smooth(data)
@ -76,9 +77,11 @@ class AoA:
2j * np.pi * config.CENTRAL_FREQUENCY_HZ * config.ANTENNA_SPACING / config.C 2j * np.pi * config.CENTRAL_FREQUENCY_HZ * config.ANTENNA_SPACING / config.C
) )
def steering_vector(self, theta: float, tof: float): def steering_vector(
omega_t = np.exp(-2j * np.pi * config.DELTA_F * tof) self, theta: float, tof: float
phi_theta = np.exp( ) -> npt.NDArray[np.complexfloating]:
omega_t: npt.NDArray[np.complex128] = np.exp(-2j * np.pi * config.DELTA_F * tof)
phi_theta: npt.NDArray[np.complex128] = np.exp(
2j 2j
* np.pi * np.pi
* config.CENTRAL_FREQUENCY_HZ * config.CENTRAL_FREQUENCY_HZ
@ -96,7 +99,7 @@ class AoA:
steering = antenna_v * phis steering = antenna_v * phis
return steering.T.reshape(-1) return steering.T.reshape(-1)
def evaluate(self, theta: float, tof: float): def evaluate(self, theta: float, tof: float) -> float:
try: try:
steering = self.steering_vector(theta, tof) steering = self.steering_vector(theta, tof)
steering_h = np.conj(steering).T steering_h = np.conj(steering).T
@ -109,7 +112,7 @@ class AoA:
return np.abs(c.real) return np.abs(c.real)
def test_smoothing(): def test_smoothing() -> None:
row, col = np.indices((4, 2)) row, col = np.indices((4, 2))
data = row + 1j * col data = row + 1j * col
aoa = AoA() aoa = AoA()
@ -120,10 +123,9 @@ def test_smoothing():
expected = np.hstack([H_01, H_12]) expected = np.hstack([H_01, H_12])
print(expected) print(expected)
assert np.allclose(smoothed, expected) assert np.allclose(smoothed, expected)
pass
def test_steering_vector(): def test_steering_vector() -> None:
aoa = AoA() aoa = AoA()
aoa.N_subcarriers = 10 aoa.N_subcarriers = 10
aoa.N_rx = 2 aoa.N_rx = 2

View File

@ -1,11 +1,11 @@
import numpy as np
import logging import logging
from queue import Queue from queue import Queue
from .. import config
import numpy.typing as npt
from scipy.signal import correlate
from scipy.signal import butter, sosfilt_zi, sosfilt
import numpy as np
import numpy.typing as npt
from scipy.signal import butter, correlate, sosfilt, sosfilt_zi
from .. import config
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
@ -14,7 +14,7 @@ np.seterr(invalid="ignore")
class Preprocessor: class Preprocessor:
def __init__(self): def __init__(self) -> None:
self.prev_entries: Queue[npt.NDArray[np.complex128]] = Queue(maxsize=100) self.prev_entries: Queue[npt.NDArray[np.complex128]] = Queue(maxsize=100)
self.short_term_avg = np.zeros((1,), dtype=np.complex128) self.short_term_avg = np.zeros((1,), dtype=np.complex128)
self.long_term_avg = np.zeros((1,), dtype=np.complex128) self.long_term_avg = np.zeros((1,), dtype=np.complex128)