format: re-format with ruff and isort
This commit is contained in:
parent
5a481ae13b
commit
8e1a849b7c
@ -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"
|
||||||
|
|||||||
17
src/cli.py
17
src/cli.py
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user