refactor: processing in separate thread
This commit is contained in:
parent
06aca17bee
commit
274f4d8492
35
src/__main__.py
Normal file
35
src/__main__.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from . import ingest
|
||||||
|
from . import config
|
||||||
|
from . import visualise
|
||||||
|
from .preprocess import Preprocessor
|
||||||
|
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s %(name)-40s %(levelname)-8s %(message)s",
|
||||||
|
)
|
||||||
|
|
||||||
|
receivers = [ingest.FeitReceiver(ip) for ip in config.RECEIVE_IP_ADDRESS_LIST]
|
||||||
|
transmitter = ingest.FeitTransmitter()
|
||||||
|
preprocess = Preprocessor()
|
||||||
|
|
||||||
|
processor = ingest.CSIProcessor()
|
||||||
|
|
||||||
|
# Start webapp in background thread
|
||||||
|
visualise.aoa = processor.aoa
|
||||||
|
webapp = threading.Thread(target=visualise.start)
|
||||||
|
webapp.start()
|
||||||
|
|
||||||
|
receiver_threads = [
|
||||||
|
threading.Thread(target=server.listen, args=(processor.add_data,))
|
||||||
|
for server in receivers
|
||||||
|
]
|
||||||
|
|
||||||
|
for thread in receiver_threads:
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
processing_thread = threading.Thread(target=processor.process_forever)
|
||||||
|
processing_thread.start()
|
||||||
127
src/ingest.py
Normal file
127
src/ingest.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
from typing import Callable
|
||||||
|
import threading
|
||||||
|
import struct
|
||||||
|
from datetime import datetime
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from .csi import CSI
|
||||||
|
from .aoa import AoA
|
||||||
|
from .preprocess import Preprocessor
|
||||||
|
from . import config
|
||||||
|
from . import visualise
|
||||||
|
|
||||||
|
|
||||||
|
class FeitTransmitter:
|
||||||
|
def __init__(self):
|
||||||
|
self.inject_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
self.inject_server.connect((config.INJECT_IP_ADDRESS, config.FEITCSI_PORT))
|
||||||
|
inject_start_string = (
|
||||||
|
f"feitcsi --frequency {config.CENTRAL_FREQUENCY_MHZ} "
|
||||||
|
f"--channel-width {config.CHANNEL_WIDTH} "
|
||||||
|
f"--format {config.FRAME_FORMAT} "
|
||||||
|
f"--mode inject -s 1 --verbose "
|
||||||
|
f"--inject-delay {1_000_000 // config.SAMPLE_RATE}"
|
||||||
|
)
|
||||||
|
self.inject_server.send(b"stop\n")
|
||||||
|
self.inject_server.send(inject_start_string.encode())
|
||||||
|
|
||||||
|
|
||||||
|
class FeitReceiver:
|
||||||
|
def __init__(self, host: str):
|
||||||
|
self.host = host
|
||||||
|
self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
self.server.connect((self.host, config.FEITCSI_PORT))
|
||||||
|
self.start_string = (
|
||||||
|
f"feitcsi --frequency {config.CENTRAL_FREQUENCY_MHZ} "
|
||||||
|
f"--channel-width {config.CHANNEL_WIDTH} "
|
||||||
|
f"--format {config.FRAME_FORMAT} "
|
||||||
|
f"--mode measure"
|
||||||
|
)
|
||||||
|
self.server.send(b"stop\n")
|
||||||
|
self.server.send(self.start_string.encode())
|
||||||
|
self.logger = logging.getLogger(
|
||||||
|
f"{__name__}.{self.__class__.__name__}-{self.host}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def listen(self, callback: Callable[[str, CSI], None]):
|
||||||
|
prev_time = datetime.now()
|
||||||
|
while True:
|
||||||
|
# This is the max size of a UDP packet. The size of the actual CSI
|
||||||
|
# packet will depend on the frame format and channel width, which
|
||||||
|
# changes the number of subcarriers
|
||||||
|
data = self.server.recv(65535)
|
||||||
|
try:
|
||||||
|
csidata = CSI(data)
|
||||||
|
self.logger.debug(
|
||||||
|
f"Received CSI data after {datetime.now() - prev_time}"
|
||||||
|
)
|
||||||
|
prev_time = datetime.now()
|
||||||
|
callback(self.host, csidata)
|
||||||
|
except struct.error:
|
||||||
|
self.logger.error("Failed to parse CSI data")
|
||||||
|
|
||||||
|
|
||||||
|
class CSIProcessor:
|
||||||
|
def __init__(self):
|
||||||
|
self.pending_data: dict[str, tuple[datetime, CSI]] = {}
|
||||||
|
self.pending_data_lock = threading.Lock()
|
||||||
|
self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
|
||||||
|
self.last_processed = datetime.now()
|
||||||
|
|
||||||
|
self.preprocess = Preprocessor()
|
||||||
|
self.aoa = AoA()
|
||||||
|
|
||||||
|
def add_data(self, host: str, data: CSI):
|
||||||
|
if (
|
||||||
|
host in self.pending_data
|
||||||
|
and self.last_processed < self.pending_data[host][0]
|
||||||
|
):
|
||||||
|
self.logger.warning(
|
||||||
|
f"Skipping data from {host} at {self.pending_data[host][0]}"
|
||||||
|
)
|
||||||
|
|
||||||
|
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):
|
||||||
|
for ip in config.RECEIVE_IP_ADDRESS_LIST:
|
||||||
|
if (
|
||||||
|
ip not in self.pending_data
|
||||||
|
or self.pending_data[ip][0] <= self.last_processed
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def process_data(self):
|
||||||
|
self.last_processed = datetime.now()
|
||||||
|
antenna_data = [
|
||||||
|
np.expand_dims(self.pending_data[ip][1].matrix[:, antenna], axis=2)
|
||||||
|
for ip, antenna in config.ANTENNA_ORDER
|
||||||
|
]
|
||||||
|
|
||||||
|
# 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}")
|
||||||
|
|
||||||
|
processed = self.preprocess.preprocess(all_data)
|
||||||
|
visualise.add_data(all_data, processed)
|
||||||
|
self.aoa.update(processed)
|
||||||
|
|
||||||
|
def process_forever(self):
|
||||||
|
while True:
|
||||||
|
if self.is_ready():
|
||||||
|
self.process_data()
|
||||||
|
else:
|
||||||
|
self.logger.debug("Not all data is ready")
|
||||||
|
time.sleep(0.001)
|
||||||
134
src/main.py
134
src/main.py
@ -1,134 +0,0 @@
|
|||||||
import logging
|
|
||||||
import socket
|
|
||||||
from typing import Callable
|
|
||||||
import threading
|
|
||||||
import struct
|
|
||||||
from datetime import datetime
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from .csi import CSI
|
|
||||||
from .aoa import AoA
|
|
||||||
from .preprocess import Preprocessor
|
|
||||||
from . import config
|
|
||||||
from . import visualise
|
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO,
|
|
||||||
format="%(asctime)s %(name)-8s %(levelname)-8s %(message)s",
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class FeitTransmitter:
|
|
||||||
def __init__(self):
|
|
||||||
self.inject_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
||||||
self.inject_server.connect((config.INJECT_IP_ADDRESS, config.FEITCSI_PORT))
|
|
||||||
inject_start_string = (
|
|
||||||
f"feitcsi --frequency {config.CENTRAL_FREQUENCY_MHZ} "
|
|
||||||
f"--channel-width {config.CHANNEL_WIDTH} "
|
|
||||||
f"--format {config.FRAME_FORMAT} "
|
|
||||||
f"--mode inject -s 1 --verbose "
|
|
||||||
f"--inject-delay {1_000_000 // config.SAMPLE_RATE}"
|
|
||||||
)
|
|
||||||
self.inject_server.send(b"stop\n")
|
|
||||||
self.inject_server.send(inject_start_string.encode())
|
|
||||||
|
|
||||||
|
|
||||||
class FeitServer:
|
|
||||||
def __init__(self, host: str):
|
|
||||||
self.host = host
|
|
||||||
self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
||||||
self.server.connect((self.host, config.FEITCSI_PORT))
|
|
||||||
self.start_string = (
|
|
||||||
f"feitcsi --frequency {config.CENTRAL_FREQUENCY_MHZ} "
|
|
||||||
f"--channel-width {config.CHANNEL_WIDTH} "
|
|
||||||
f"--format {config.FRAME_FORMAT} "
|
|
||||||
f"--mode measure"
|
|
||||||
)
|
|
||||||
self.server.send(b"stop\n")
|
|
||||||
self.server.send(self.start_string.encode())
|
|
||||||
|
|
||||||
def listen(self, callback: Callable[[str, CSI], None]):
|
|
||||||
prev_time = datetime.now()
|
|
||||||
while True:
|
|
||||||
# This is the max size of a UDP packet. The size of the actual CSI
|
|
||||||
# packet will depend on the frame format and channel width, which
|
|
||||||
# changes the number of subcarriers
|
|
||||||
data = self.server.recv(65535)
|
|
||||||
try:
|
|
||||||
csidata = CSI(data)
|
|
||||||
logger.debug(f"Received CSI data after {datetime.now() - prev_time}")
|
|
||||||
prev_time = datetime.now()
|
|
||||||
callback(self.host, csidata)
|
|
||||||
except struct.error:
|
|
||||||
logger.error("Failed to parse CSI data")
|
|
||||||
|
|
||||||
|
|
||||||
pending_data: dict[str, tuple[datetime, CSI]] = {}
|
|
||||||
pending_data_lock = threading.Lock()
|
|
||||||
|
|
||||||
|
|
||||||
def process_data(host: str, data: CSI):
|
|
||||||
global pending_data
|
|
||||||
current_time = datetime.now()
|
|
||||||
|
|
||||||
logger.debug(f"Got CSI data from {host}")
|
|
||||||
|
|
||||||
with pending_data_lock:
|
|
||||||
if host in pending_data and (
|
|
||||||
current_time - pending_data[host][0]
|
|
||||||
).total_seconds() < 1 / (2 * config.SAMPLE_RATE):
|
|
||||||
logger.warning(f"Received CSI data from {host} too quickly")
|
|
||||||
return
|
|
||||||
elif host in pending_data:
|
|
||||||
logger.warning(f"Skipping existing CSI data from {host}")
|
|
||||||
pending_data[host] = (datetime.now(), data)
|
|
||||||
|
|
||||||
for ip in config.RECEIVE_IP_ADDRESS_LIST:
|
|
||||||
if ip not in pending_data:
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
"Antenna RSSI values:"
|
|
||||||
+ str(
|
|
||||||
[
|
|
||||||
(ip, csi.header.rssi1, csi.header.rssi2)
|
|
||||||
for ip, (_, csi) in sorted(pending_data.items())
|
|
||||||
]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
antenna_data = [
|
|
||||||
np.expand_dims(pending_data[ip][1].matrix[:, antenna], axis=2)
|
|
||||||
for ip, antenna in config.ANTENNA_ORDER
|
|
||||||
]
|
|
||||||
|
|
||||||
# We have data from all servers
|
|
||||||
all_data = np.concat(antenna_data, axis=1)
|
|
||||||
logger.info(f"Got final CSI data with shape {all_data.shape}")
|
|
||||||
pending_data = {}
|
|
||||||
|
|
||||||
# logging.info(f"Got CSI frame with shape {data.matrix.shape}")
|
|
||||||
processed = preprocess.preprocess(all_data)
|
|
||||||
visualise.add_data(data.matrix, processed)
|
|
||||||
aoa.update(processed)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
servers = [FeitServer(ip) for ip in config.RECEIVE_IP_ADDRESS_LIST]
|
|
||||||
transmitter = FeitTransmitter()
|
|
||||||
preprocess = Preprocessor()
|
|
||||||
aoa = AoA()
|
|
||||||
|
|
||||||
# Start webapp in background thread
|
|
||||||
visualise.aoa = aoa
|
|
||||||
webapp = threading.Thread(target=visualise.start)
|
|
||||||
webapp.start()
|
|
||||||
|
|
||||||
server_threads = [
|
|
||||||
threading.Thread(target=server.listen, args=(process_data,))
|
|
||||||
for server in servers
|
|
||||||
]
|
|
||||||
for thread in server_threads:
|
|
||||||
thread.start()
|
|
||||||
Loading…
Reference in New Issue
Block a user