Compare commits

..

10 Commits
main ... Ufa

@ -253,15 +253,29 @@ SERVER_PORT_2=8080
#################
# NN_SERVER
NN_MODEL_2400=ensemble_2_pic
NN_WEIGHTS_2400=${PATH_TO_NN}ensemble2400_2pic.pth
NN_CLASSES_2400=drone,noise
NN_MODEL_1200=ensemble_2_pic
NN_WEIGHTS_1200=${PATH_TO_NN}ensemble1200_2pic.pth
NN_CLASSES_1200=drone,noise
NN_MODEL_915=ensemble_2_pic
NN_WEIGHTS_915=${PATH_TO_NN}ensemble915_2pic.pth
NN_CLASSES_915=drone,noise
NN_BUILD_FUNC=build_func_ensemble
NN_PRE_FUNC=pre_func_ensemble
NN_INFERENCE_FUNC=inference_func_ensemble
NN_POST_FUNC=post_func_ensemble
NN_SYNTHETIC_EXAMPLES=10
NN_SYNTHETIC_MIX_COUNT=1
NN_SRC_DATASET=/app/NN_server/datasets/full_dataset/
#################
FREQS=915,1200,2400
PATH_TO_NN=/app/NN_server/NN/
SRC_RESULT=/app/NN_server/result/
SRC_EXAMPLE=${PATH_TO_NN}example/
NN_1='${PATH_TO_NN}resnet18_1.pth && ${PATH_TO_NN}config_resnet18.yaml && ${SRC_EXAMPLE} && ${SRC_RESULT} && Resnet18_1_2400 && build_func_resnet18 && pre_func_resnet18 && inference_func_resnet18 && post_func_resnet18 && [drone,noise,wifi] && 10 && 1 && /app/NN_server/datasets/full_dataset_pic/'
NN_21='${PATH_TO_NN}ensemble_1.2.pth && ${PATH_TO_NN}config_ensemble.yaml && ${SRC_EXAMPLE} && ${SRC_RESULT} && ensemble_1200 && build_func_ensemble && pre_func_ensemble && inference_func_ensemble && post_func_ensemble && [drone,noise] && 10 && 1 && /app/NN_server/datasets/full_dataset/'
NN_22='${PATH_TO_NN}ensemble_915.pth && ${PATH_TO_NN}config_ensemble.yaml && ${SRC_EXAMPLE} && ${SRC_RESULT} && ensemble_915 && build_func_ensemble && pre_func_ensemble && inference_func_ensemble && post_func_ensemble && [drone,noise] && 10 && 1 && /app/NN_server/datasets/full_dataset/'
GENERAL_SERVER_IP=dronedetector-server-to-master
GENERAL_SERVER_PORT=5010

@ -99,10 +99,11 @@ class Model(object):
except Exception as exc:
print(str(exc))
def __init__(self, file_model='', file_config='', src_example='', src_result='', type_model='',
def __init__(self, freq=0, file_model='', file_config='', src_example='', src_result='', type_model='',
build_model_func=None, pre_func=None, inference_func=None, post_func=None, classes=None,
number_synthetic_examples=0, number_src_data_for_one_synthetic_example=0, path_to_src_dataset=''):
try:
self._freq = int(freq)
self._file_model = file_model
self._file_config = file_config
self._src_example = src_example
@ -137,6 +138,9 @@ class Model(object):
def get_mapping(self):
return list(self._classes.values())
def get_freq(self):
return self._freq
def get_model_name(self):
return self._type_model

@ -0,0 +1 @@
from Models.ensemble_2_pic import *

@ -0,0 +1 @@
from Models.ensemble_2_pic import *

@ -1,197 +0,0 @@
from torchvision import models
import torch.nn as nn
import matplotlib
import numpy as np
import torch
import cv2
import gc
import io
def _render_plot(values, figsize=(16, 16), dpi=16):
import matplotlib.pyplot as plt
fig = plt.figure(figsize=figsize)
plt.axes(ylim=(-1, 1))
plt.plot(values, color="black")
plt.gca().set_axis_off()
plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0)
plt.margins(0, 0)
buf = io.BytesIO()
fig.savefig(buf, format="png", dpi=dpi)
buf.seek(0)
img_arr = np.frombuffer(buf.getvalue(), dtype=np.uint8)
buf.close()
img = cv2.imdecode(img_arr, 1)
if img is None:
raise RuntimeError("failed to decode plot image")
plt.clf()
plt.cla()
plt.close()
plt.close(fig)
return np.asarray(cv2.split(img), dtype=np.float32)
def pre_func_ensemble(data=None, src="", ind_inference=0):
try:
import matplotlib.pyplot as plt
matplotlib.use("Agg")
plt.ioff()
real = np.asarray(data[0], dtype=np.float32)
imag = np.asarray(data[1], dtype=np.float32)
signal = real + 1j * imag
img_real = _render_plot(signal.real)
img_mag = _render_plot(np.abs(signal))
cv2.destroyAllWindows()
gc.collect()
print("Подготовка данных завершена")
print()
return [img_real, img_mag]
except Exception as exc:
print(str(exc))
return None
def build_func_ensemble(file_model="", file_config="", num_classes=None):
try:
import matplotlib.pyplot as plt
matplotlib.use("Agg")
plt.ioff()
torch.cuda.empty_cache()
num_classes = 2
model1 = models.resnet18(pretrained=False)
model2 = models.resnet50(pretrained=False)
model1.fc = nn.Linear(model1.fc.in_features, num_classes)
model2.fc = nn.Linear(model2.fc.in_features, num_classes)
class Ensemble(nn.Module):
def __init__(self, model1, model2):
super().__init__()
self.model1 = model1
self.model2 = model2
self.fc = nn.Linear(2 * num_classes, num_classes)
def forward(self, x):
if isinstance(x, (list, tuple)):
x1 = x[0]
x2 = x[1] if len(x) > 1 else x[0]
else:
x1 = x
x2 = x
y1 = self.model1(x1)
y2 = self.model2(x2)
y = torch.cat((y1, y2), dim=1)
return self.fc(y)
model = Ensemble(model1, model2)
device = "cuda" if torch.cuda.is_available() else "cpu"
if device != "cpu":
model = model.to(device)
model.load_state_dict(torch.load(file_model, map_location=device))
model.eval()
cv2.destroyAllWindows()
gc.collect()
print("Инициализация модели завершена")
print()
return model
except Exception as exc:
print(str(exc))
return None
def inference_func_ensemble(data=None, model=None, mapping=None, shablon=""):
try:
cv2.destroyAllWindows()
gc.collect()
torch.cuda.empty_cache()
device = "cuda" if torch.cuda.is_available() else "cpu"
if isinstance(data, (list, tuple)) and len(data) >= 2:
inputs = [
torch.unsqueeze(torch.tensor(data[0]).cpu(), 0).to(device).float(),
torch.unsqueeze(torch.tensor(data[1]).cpu(), 0).to(device).float(),
]
else:
tensor = torch.unsqueeze(torch.tensor(data).cpu(), 0).to(device).float()
inputs = [tensor, tensor]
with torch.no_grad():
output = model(inputs)
_, predict = torch.max(output.data, 1)
prediction = mapping[int(np.asarray(predict.cpu())[0])]
print("PREDICTION" + shablon + ": " + prediction)
output = output.cpu()
label = np.asarray(np.argmax(output, axis=1))[0]
output = np.asarray(torch.squeeze(output, 0))
expon = np.exp(output - np.max(output))
probability = round((expon / expon.sum())[label], 2)
cv2.destroyAllWindows()
gc.collect()
print("Уверенность" + shablon + " в предсказании: " + str(probability))
print("Инференс завершен")
print()
return [prediction, probability]
except Exception as exc:
print(str(exc))
return None
def post_func_ensemble(src="", model_type="", prediction="", model_id=0, ind_inference=0, data=None):
try:
import matplotlib.pyplot as plt
matplotlib.use("Agg")
plt.ioff()
if int(ind_inference) <= 100 and isinstance(data, (list, tuple)) and len(data) >= 2:
fig, ax = plt.subplots()
ax.imshow(np.moveaxis(data[0], 0, -1))
plt.savefig(src + "_inference_" + str(ind_inference) + "_" + prediction + "_real_" + str(model_id) + "_" + model_type + ".png")
plt.clf()
plt.cla()
plt.close(fig)
cv2.destroyAllWindows()
gc.collect()
fig, ax = plt.subplots()
ax.imshow(np.moveaxis(data[1], 0, -1))
plt.savefig(src + "_inference_" + str(ind_inference) + "_" + prediction + "_mod_" + str(model_id) + "_" + model_type + ".png")
plt.clf()
plt.cla()
plt.close(fig)
cv2.destroyAllWindows()
gc.collect()
plt.clf()
plt.cla()
plt.close()
cv2.destroyAllWindows()
gc.collect()
print("Постобработка завершена")
print()
except Exception as exc:
print(str(exc))
return None

@ -0,0 +1 @@
from Models.ensemble_2_pic import *

@ -1,197 +0,0 @@
from torchvision import models
import torch.nn as nn
import matplotlib
import numpy as np
import torch
import cv2
import gc
import io
def _render_plot(values, figsize=(16, 16), dpi=16):
import matplotlib.pyplot as plt
fig = plt.figure(figsize=figsize)
plt.axes(ylim=(-1, 1))
plt.plot(values, color="black")
plt.gca().set_axis_off()
plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0)
plt.margins(0, 0)
buf = io.BytesIO()
fig.savefig(buf, format="png", dpi=dpi)
buf.seek(0)
img_arr = np.frombuffer(buf.getvalue(), dtype=np.uint8)
buf.close()
img = cv2.imdecode(img_arr, 1)
if img is None:
raise RuntimeError("failed to decode plot image")
plt.clf()
plt.cla()
plt.close()
plt.close(fig)
return np.asarray(cv2.split(img), dtype=np.float32)
def pre_func_ensemble(data=None, src="", ind_inference=0):
try:
import matplotlib.pyplot as plt
matplotlib.use("Agg")
plt.ioff()
real = np.asarray(data[0], dtype=np.float32)
imag = np.asarray(data[1], dtype=np.float32)
signal = real + 1j * imag
img_real = _render_plot(signal.real)
img_mag = _render_plot(np.abs(signal))
cv2.destroyAllWindows()
gc.collect()
print("Подготовка данных завершена")
print()
return [img_real, img_mag]
except Exception as exc:
print(str(exc))
return None
def build_func_ensemble(file_model="", file_config="", num_classes=None):
try:
import matplotlib.pyplot as plt
matplotlib.use("Agg")
plt.ioff()
torch.cuda.empty_cache()
num_classes = 2
model1 = models.resnet18(pretrained=False)
model2 = models.resnet50(pretrained=False)
model1.fc = nn.Linear(model1.fc.in_features, num_classes)
model2.fc = nn.Linear(model2.fc.in_features, num_classes)
class Ensemble(nn.Module):
def __init__(self, model1, model2):
super().__init__()
self.model1 = model1
self.model2 = model2
self.fc = nn.Linear(2 * num_classes, num_classes)
def forward(self, x):
if isinstance(x, (list, tuple)):
x1 = x[0]
x2 = x[1] if len(x) > 1 else x[0]
else:
x1 = x
x2 = x
y1 = self.model1(x1)
y2 = self.model2(x2)
y = torch.cat((y1, y2), dim=1)
return self.fc(y)
model = Ensemble(model1, model2)
device = "cuda" if torch.cuda.is_available() else "cpu"
if device != "cpu":
model = model.to(device)
model.load_state_dict(torch.load(file_model, map_location=device))
model.eval()
cv2.destroyAllWindows()
gc.collect()
print("Инициализация модели завершена")
print()
return model
except Exception as exc:
print(str(exc))
return None
def inference_func_ensemble(data=None, model=None, mapping=None, shablon=""):
try:
cv2.destroyAllWindows()
gc.collect()
torch.cuda.empty_cache()
device = "cuda" if torch.cuda.is_available() else "cpu"
if isinstance(data, (list, tuple)) and len(data) >= 2:
inputs = [
torch.unsqueeze(torch.tensor(data[0]).cpu(), 0).to(device).float(),
torch.unsqueeze(torch.tensor(data[1]).cpu(), 0).to(device).float(),
]
else:
tensor = torch.unsqueeze(torch.tensor(data).cpu(), 0).to(device).float()
inputs = [tensor, tensor]
with torch.no_grad():
output = model(inputs)
_, predict = torch.max(output.data, 1)
prediction = mapping[int(np.asarray(predict.cpu())[0])]
print("PREDICTION" + shablon + ": " + prediction)
output = output.cpu()
label = np.asarray(np.argmax(output, axis=1))[0]
output = np.asarray(torch.squeeze(output, 0))
expon = np.exp(output - np.max(output))
probability = round((expon / expon.sum())[label], 2)
cv2.destroyAllWindows()
gc.collect()
print("Уверенность" + shablon + " в предсказании: " + str(probability))
print("Инференс завершен")
print()
return [prediction, probability]
except Exception as exc:
print(str(exc))
return None
def post_func_ensemble(src="", model_type="", prediction="", model_id=0, ind_inference=0, data=None):
try:
import matplotlib.pyplot as plt
matplotlib.use("Agg")
plt.ioff()
if int(ind_inference) <= 100 and isinstance(data, (list, tuple)) and len(data) >= 2:
fig, ax = plt.subplots()
ax.imshow(np.moveaxis(data[0], 0, -1))
plt.savefig(src + "_inference_" + str(ind_inference) + "_" + prediction + "_real_" + str(model_id) + "_" + model_type + ".png")
plt.clf()
plt.cla()
plt.close(fig)
cv2.destroyAllWindows()
gc.collect()
fig, ax = plt.subplots()
ax.imshow(np.moveaxis(data[1], 0, -1))
plt.savefig(src + "_inference_" + str(ind_inference) + "_" + prediction + "_mod_" + str(model_id) + "_" + model_type + ".png")
plt.clf()
plt.cla()
plt.close(fig)
cv2.destroyAllWindows()
gc.collect()
plt.clf()
plt.cla()
plt.close()
cv2.destroyAllWindows()
gc.collect()
print("Постобработка завершена")
print()
except Exception as exc:
print(str(exc))
return None

@ -3,7 +3,6 @@ from dotenv import dotenv_values
from common.runtime import load_root_env, validate_env, as_int, as_str
import os
import sys
import re
import matplotlib.pyplot as plt
from Model import Model
import numpy as np
@ -11,25 +10,19 @@ import matplotlib
import importlib
import threading
import requests
import asyncio
import shutil
import json
import gc
import logging
import time
TORCHSIG_PATH = "/app/torchsig"
if TORCHSIG_PATH not in sys.path:
# Ensure import torchsig resolves to /app/torchsig/torchsig package.
sys.path.insert(0, TORCHSIG_PATH)
logging.basicConfig(level=logging.INFO)
app = Flask(__name__)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
queue = asyncio.Queue()
semaphore = asyncio.Semaphore(3)
prediction_list = []
result_msg = {}
results = []
@ -44,38 +37,39 @@ validate_env("NN_server/server.py", {
"GENERAL_SERVER_PORT": as_int,
"SERVER_IP": as_str,
"SERVER_PORT": as_int,
"PATH_TO_NN": as_str,
"SRC_RESULT": as_str,
"SRC_EXAMPLE": as_str,
"FREQS": as_str,
})
config = dict(dotenv_values(ROOT_ENV))
def is_model_config_key(key, value):
return bool(re.fullmatch(r"NN_\d+", key or "")) and isinstance(value, str) and " && " in value
def get_required_drone_streak(freq):
return config.get(f"DRONE_STREAK_{freq}", "1")
def get_required_drone_streak(freq):
raw_value = config.get(f"DRONE_STREAK_{freq}", "1")
try:
return max(1, int(raw_value))
except (TypeError, ValueError):
logging.warning("Invalid DRONE_STREAK_%s=%r, falling back to 1", freq, raw_value)
return 1
def get_required_drone_prob(freq):
return config.get(f"DRONE_PROB_THRESHOLD_{freq}", config.get("DRONE_PROB_THRESHOLD_DEFAULT", "0"))
def update_drone_streak(freq, prediction):
if prediction == "drone":
def update_drone_streak(freq, prediction, drone_probability):
required_prob = get_required_drone_prob(freq)
drone_probability = 0.0 if drone_probability is None else float(drone_probability)
passes_prob_gate = prediction == "drone" and drone_probability >= required_prob
if passes_prob_gate:
drone_streaks[freq] = drone_streaks.get(freq, 0) + 1
else:
drone_streaks[freq] = 0
required = get_required_drone_streak(freq)
triggered = prediction == "drone" and drone_streaks[freq] >= required
triggered = passes_prob_gate and drone_streaks[freq] >= required
logging.info(
"NN alarm gate freq=%s prediction=%s streak=%s/%s triggered=%s",
"NN alarm gate freq=%s prediction=%s drone_probability=%.3f threshold=%.3f streak=%s/%s triggered=%s",
freq,
prediction,
drone_probability,
required_prob,
drone_streaks[freq],
required,
triggered,
@ -83,52 +77,171 @@ def update_drone_streak(freq, prediction):
return 8 if triggered else 0
def parse_freqs(raw_value):
freqs = []
for item in (raw_value or "").split(','):
item = item.strip()
if not item:
continue
freqs.append(int(item))
if not freqs:
raise RuntimeError("[NN_server/server.py] no NN frequencies configured in FREQS")
return freqs
def parse_classes(raw_value):
if raw_value is None:
raise RuntimeError("[NN_server/server.py] model classes are missing")
value = raw_value.strip()
if value.startswith('[') and value.endswith(']'):
value = value[1:-1]
classes = {}
for class_name in value.split(','):
class_name = class_name.strip()
if class_name:
classes[len(classes)] = class_name
if not classes:
raise RuntimeError("[NN_server/server.py] no classes parsed from NN_CLASSES_*")
return classes
def get_required_config(key):
value = config.get(key)
if value is None:
raise RuntimeError(f"[NN_server/server.py] missing required env key: {key}")
value = str(value).strip()
if not value:
raise RuntimeError(f"[NN_server/server.py] empty required env key: {key}")
return value
def get_optional_config(key, default=''):
value = config.get(key)
if value is None:
return default
return str(value).strip()
def build_model_specs():
build_func_name = get_optional_config('NN_BUILD_FUNC', 'build_func_ensemble')
pre_func_name = get_optional_config('NN_PRE_FUNC', 'pre_func_ensemble')
inference_func_name = get_optional_config('NN_INFERENCE_FUNC', 'inference_func_ensemble')
post_func_name = get_optional_config('NN_POST_FUNC', 'post_func_ensemble')
src_example = get_optional_config('NN_SRC_EXAMPLE', config['SRC_EXAMPLE'])
src_result = get_optional_config('NN_SRC_RESULT', config['SRC_RESULT'])
synthetic_examples = int(get_optional_config('NN_SYNTHETIC_EXAMPLES', '0'))
synthetic_mix_count = int(get_optional_config('NN_SYNTHETIC_MIX_COUNT', '1'))
src_dataset = get_optional_config('NN_SRC_DATASET', '')
specs = []
for freq in parse_freqs(config.get('NN_FREQS', config.get('FREQS', ''))):
module_name = get_required_config(f'NN_MODEL_{freq}')
weights = get_required_config(f'NN_WEIGHTS_{freq}')
classes = parse_classes(get_required_config(f'NN_CLASSES_{freq}'))
file_config = get_optional_config(f'NN_CONFIG_{freq}', get_optional_config('NN_CONFIG', ''))
specs.append({
'freq': freq,
'module_name': module_name,
'weights': weights,
'config': file_config,
'classes': classes,
'src_example': src_example,
'src_result': src_result,
'build_func_name': build_func_name,
'pre_func_name': pre_func_name,
'inference_func_name': inference_func_name,
'post_func_name': post_func_name,
'synthetic_examples': synthetic_examples,
'synthetic_mix_count': synthetic_mix_count,
'src_dataset': src_dataset,
})
return specs
if not config:
raise RuntimeError("[NN_server/server.py] .env was loaded but no keys were parsed")
if not any(is_model_config_key(key, value) for key, value in config.items()):
raise RuntimeError("[NN_server/server.py] no NN_* model entries configured")
logging.info("NN config loaded from %s", ROOT_ENV)
gen_server_ip = config['GENERAL_SERVER_IP']
gen_server_port = config['GENERAL_SERVER_PORT']
drone_streaks = {}
MODEL_SPECS = build_model_specs()
INFERENCE_TELEMETRY_HOST = os.getenv('telemetry_host', '127.0.0.1')
INFERENCE_TELEMETRY_PORT = os.getenv('telemetry_port', '5020')
INFERENCE_TELEMETRY_ENDPOINT = os.getenv('telemetry_inference_endpoint', 'inference/result')
INFERENCE_TELEMETRY_TIMEOUT_SEC = float(os.getenv('telemetry_inference_timeout_sec', '0.30'))
def recreate_directory(path):
if os.path.isdir(path):
shutil.rmtree(path)
os.makedirs(path, exist_ok=True)
def get_result_dir():
if not MODEL_SPECS:
return ''
return MODEL_SPECS[0]['src_result']
def collect_inference_images(result_id):
result_dir = get_result_dir()
if not result_dir or not os.path.isdir(result_dir):
return []
needle = f"_inference_{result_id}_"
images = []
for name in sorted(os.listdir(result_dir)):
if needle in name and name.endswith('.png'):
images.append(name)
return images
def send_inference_result(payload):
try:
requests.post(
"http://{0}:{1}/{2}".format(
INFERENCE_TELEMETRY_HOST,
INFERENCE_TELEMETRY_PORT,
INFERENCE_TELEMETRY_ENDPOINT.lstrip('/'),
),
json=payload,
timeout=INFERENCE_TELEMETRY_TIMEOUT_SEC,
)
except Exception as exc:
print(str(exc))
def init_data_for_inference():
try:
if os.path.isdir(config['SRC_RESULT']):
shutil.rmtree(config['SRC_RESULT'])
os.mkdir(config['SRC_RESULT'])
if os.path.isdir(config['SRC_EXAMPLE']):
shutil.rmtree(config['SRC_EXAMPLE'])
os.mkdir(config['SRC_EXAMPLE'])
if MODEL_SPECS:
recreate_directory(MODEL_SPECS[0]['src_result'])
recreate_directory(MODEL_SPECS[0]['src_example'])
except Exception as exc:
print(str(exc))
print()
try:
global model_list
for key, value in config.items():
if is_model_config_key(key, value):
params = value.split(' && ')
module = importlib.import_module('Models.' + params[4])
classes = {}
for value in params[9][1:-1].split(','):
classes[len(classes)] = value
model = Model(file_model=params[0], file_config=params[1], src_example=params[2], src_result=params[3],
type_model=params[4], build_model_func=getattr(module, params[5]),
pre_func=getattr(module, params[6]), inference_func=getattr(module, params[7]),
post_func=getattr(module, params[8]), classes=classes, number_synthetic_examples=int(params[10]),
number_src_data_for_one_synthetic_example=int(params[11]), path_to_src_dataset=params[12])
model_list.append(model)
# if key.startswith('ALG_'):
# params = config[key].split(' && ')
# module = importlib.import_module('Algorithms.' + params[2])
# classes = {}
# for value in params[6][1:-1].split(','):
# classes[len(classes)] = value
# alg = Algorithm(src_example=params[0], src_result=params[1], type_alg=params[2], pre_func=getattr(module, params[3]),
# inference_func=getattr(module, params[4]), post_func=getattr(module, params[5]), classes=classes,
# number_synthetic_examples=int(params[7]), number_src_data_for_one_synthetic_example=int(params[8]), path_to_src_dataset=params[9])
# alg_list.append(alg)
model_list.clear()
for spec in MODEL_SPECS:
module = importlib.import_module('Models.' + spec['module_name'])
model = Model(
freq=spec['freq'],
file_model=spec['weights'],
file_config=spec['config'],
src_example=spec['src_example'],
src_result=spec['src_result'],
type_model=f"{spec['module_name']}@{spec['freq']}",
build_model_func=getattr(module, spec['build_func_name']),
pre_func=getattr(module, spec['pre_func_name']),
inference_func=getattr(module, spec['inference_func_name']),
post_func=getattr(module, spec['post_func_name']),
classes=spec['classes'],
number_synthetic_examples=spec['synthetic_examples'],
number_src_data_for_one_synthetic_example=spec['synthetic_mix_count'],
path_to_src_dataset=spec['src_dataset'],
)
model_list.append(model)
except Exception as exc:
print(str(exc))
print()
@ -142,53 +255,82 @@ def run_example():
print(str(exc))
def find_model_for_freq(freq):
for model in model_list:
if model.get_freq() == freq:
return model
return None
@app.route('/receive_data', methods=['POST'])
def receive_data():
try:
print()
data = json.loads(request.json)
data = request.json
if isinstance(data, str):
data = json.loads(data)
print('#' * 100)
print('Получен пакет ' + str(Model.get_ind_inference()))
result_id = Model.get_ind_inference()
freq = int(data['freq'])
print('Частота: ' + str(freq))
# print('Канал: ' + str(data['channel']))
result_msg = {}
data_to_send = {}
prediction_list = []
#print(model_list)
for model in model_list:
#print(str(freq))
#print(model.get_model_name())
if str(freq) in model.get_model_name():
print('-' * 100)
print(str(model))
result_msg[str(model.get_model_name())] = {'freq': freq}
prediction, probability = model.get_inference([np.asarray(data['data_real'], dtype=np.float32), np.asarray(data['data_imag'], dtype=np.float32)])
result_msg[str(model.get_model_name())]['prediction'] = prediction
result_msg[str(model.get_model_name())]['probability'] = str(probability)
prediction_list.append(prediction)
print('-' * 100)
print()
try:
result = update_drone_streak(freq, prediction_list[0])
data_to_send={
'freq': str(freq),
'amplitude': result
#'triggered': False if result < 7 else True,
#'light_len': result
}
response = requests.post("http://{0}:{1}/process_data".format(gen_server_ip, gen_server_port), json=data_to_send)
if response.status_code == 200:
print("Данные успешно отправлены!")
print("Частота: " + str(freq))
print("Отправлено светодиодов: " + str(result))
else:
print("Ошибка при отправке данных: ", response.status_code)
except Exception as exc:
print(str(exc))
break
model = find_model_for_freq(freq)
if model is None:
raise RuntimeError(f"No NN model configured for freq={freq}")
print('-' * 100)
print(str(model))
result_msg[str(model.get_model_name())] = {'freq': freq}
inference_result = model.get_inference([
np.asarray(data['data_real'], dtype=np.float32),
np.asarray(data['data_imag'], dtype=np.float32),
])
if inference_result is None:
raise RuntimeError(f"Inference failed for {model.get_model_name()}")
prediction, probability = inference_result[:2]
drone_probability = float(probability) if prediction == "drone" else 0.0
result_msg[str(model.get_model_name())]['prediction'] = prediction
result_msg[str(model.get_model_name())]['probability'] = str(probability)
result_msg[str(model.get_model_name())]['drone_probability'] = str(drone_probability)
result_msg[str(model.get_model_name())]['drone_threshold'] = str(get_required_drone_prob(freq))
prediction_list.append(prediction)
inference_event = {
'result_id': result_id,
'ts': time.time(),
'freq': str(freq),
'model': model.get_model_name(),
'prediction': prediction,
'probability': float(probability),
'drone_probability': drone_probability,
'drone_threshold': str(get_required_drone_prob(freq)),
'images': collect_inference_images(result_id),
}
send_inference_result(inference_event)
print('-' * 100)
print()
try:
result = update_drone_streak(freq, prediction, drone_probability)
data_to_send = {
'freq': str(freq),
'amplitude': result,
}
response = requests.post(
"http://{0}:{1}/process_data".format(gen_server_ip, gen_server_port),
json=data_to_send,
)
if response.status_code == 200:
print("Данные успешно отправлены!")
print("Частота: " + str(freq))
print("Отправлено светодиодов: " + str(result))
else:
print("Ошибка при отправке данных: ", response.status_code)
except Exception as exc:
print(str(exc))
Model.get_inc_ind_inference()
print()
@ -197,11 +339,13 @@ def receive_data():
for alg in alg_list:
print('-' * 100)
print(str(alg))
alg.get_inference([np.asarray(data['data_real'], dtype=np.float32), np.asarray(data['data_imag'], dtype=np.float32)])
alg.get_inference([
np.asarray(data['data_real'], dtype=np.float32),
np.asarray(data['data_imag'], dtype=np.float32),
])
print('-' * 100)
print()
#Algorithm.get_inc_ind_inference()
print()
print('#' * 100)
@ -214,128 +358,6 @@ def receive_data():
print(str(exc))
'''
def run_flask():
app.run(host=config['SERVER_IP'], port=int(config['SERVER_PORT']))
async def process_tasks():
workers = [asyncio.create_task(worker(queue=queue, semaphore=semaphore)) for _ in range(2)]
await asyncio.gather(*workers)
async def main():
asyncio.create_task(process_tasks())
flask_thread = threading.Thread(target=run_flask)
flask_thread.start()
while True:
if queue.qsize() <= 1:
asyncio.create_task(process_tasks())
await asyncio.sleep(1)
@app.route('/receive_data', methods=['POST'])
def add_task():
queue_size = queue.qsize()
if queue_size > 1:
return {}
print()
data = json.loads(request.json)
print('#' * 100)
print('Получен пакет ' + str(Model.get_ind_inference()))
freq = int(data['freq'])
print('Частота ' + str(freq))
result_msg = {}
for model in model_list:
if str(freq) in model.get_model_name():
print('-' * 100)
print(str(model))
result_msg[str(model.get_model_name())] = {'freq': freq}
asyncio.run_coroutine_threadsafe(queue.put({'freq': freq, 'model': model, 'data': data}), loop)
do_inference(model=model, data=data, freq=freq)
break
del data
gc.collect()
return jsonify(result_msg)
async def worker(queue, semaphore):
while True:
task = await queue.get()
if task is None:
break
async with semaphore:
try:
await do_inference(model=task['model'], data=task['data'], freq=task['freq'])
except Exception as e:
print(str(e))
print(results)
queue.task_done()
async def do_inference(model=None, data=None, freq=0):
prediction_list = []
print("Длина очереди" + str(queue.qsize()))
inference(model=model, data=data, freq=freq)
try:
results = []
for pred in prediction_list:
if pred[1] == 'drone':
results.append([pred[0],8])
else:
results.append([pred[0],0])
for result in results:
try:
data_to_send={
'freq': result[0],
'amplitude': result[1],
'triggered': False if result[1] < 7 else True,
'light_len': result[1]
}
response = requests.post("http://{0}:{1}/process_data".format(gen_server_ip, gen_server_port), json=data_to_send)
await response.text
if response.status_code == 200:
print("Данные успешно отправлены!")
print("Отправлено светодиодов: " + str(data_to_send['light_len']))
else:
print("Ошибка при отправке данных: ", response.status_code)
except Exception as exc:
print(str(exc))
except Exception as exc:
print(str(exc))
Model.get_inc_ind_inference()
print()
print('#' * 100)
del data
gc.collect()
def inference(model=None, data=None, freq=0):
prediction, probability = model.get_inference([np.asarray(data['data_real'], dtype=np.float32), np.asarray(data['data_imag'], dtype=np.float32)])
result_msg[str(model.get_model_name())]['prediction'] = prediction
result_msg[str(model.get_model_name())]['probability'] = str(probability)
queue_size = queue.qsize()
print(queue_size)
prediction_list.append([freq, prediction])
print('-' * 100)
print()
if __name__ == '__main__':
init_data_for_inference()
#asyncio.run(main)
loop.run_until_complete(main())
'''
def run_flask():
print(config['SERVER_IP'])
app.run(host=config['SERVER_IP'], port=int(config['SERVER_PORT']))
@ -346,5 +368,3 @@ if __name__ == '__main__':
flask_thread = threading.Thread(target=run_flask)
flask_thread.start()
#app.run(host=config['SERVER_IP'], port=int(config['SERVER_PORT']))

@ -6,25 +6,82 @@ SCRIPT_OWNER="${CAPTURE_USER:-$(stat -c %U "$SCRIPT_DIR")}"
SCRIPT_OWNER_HOME="$(getent passwd "$SCRIPT_OWNER" | cut -d: -f6)"
cd "$SCRIPT_DIR"
source "$SCRIPT_DIR/.env"
ENV_FILE="$SCRIPT_DIR/.env"
declare -A DOTENV_VALUES
trim_whitespace() {
local value="$1"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
printf '%s' "$value"
}
load_env_file() {
local line key value quote_char
if [[ ! -f "$ENV_FILE" ]]; then
echo "Не найден .env: $ENV_FILE" >&2
exit 1
fi
while IFS= read -r line || [[ -n "$line" ]]; do
line="${line%$'\r'}"
line="$(trim_whitespace "$line")"
[[ -z "$line" || "$line" == \#* ]] && continue
if [[ "$line" == export\ * ]]; then
line="${line#export }"
fi
[[ "$line" == *=* ]] || continue
key="$(trim_whitespace "${line%%=*}")"
value="$(trim_whitespace "${line#*=}")"
if [[ ${#value} -ge 2 ]]; then
quote_char="${value:0:1}"
if [[ ( "$quote_char" == "'" || "$quote_char" == '"' ) && "${value: -1}" == "$quote_char" ]]; then
value="${value:1:${#value}-2}"
fi
fi
DOTENV_VALUES["$key"]="$value"
done < "$ENV_FILE"
}
get_env_setting() {
local key="$1"
local default="${2-}"
if [[ -n "${!key+x}" ]]; then
printf '%s' "${!key}"
elif [[ -n "${DOTENV_VALUES[$key]+x}" ]]; then
printf '%s' "${DOTENV_VALUES[$key]}"
else
printf '%s' "$default"
fi
}
load_env_file
############################
# НАСТРОЙКИ
############################
BASE_DIR="${CAPTURE_BASE_DIR:-${SCRIPT_OWNER_HOME}/dataset/noise}"
BASE_DIR="$(get_env_setting CAPTURE_BASE_DIR "${SCRIPT_OWNER_HOME}/dataset/noise")"
# Путь к python из venv
PYTHON_BIN="${PYTHON_BIN:-$SCRIPT_DIR/.venv-sdr/bin/python}"
PYTHON_BIN="$(get_env_setting PYTHON_BIN "$SCRIPT_DIR/.venv-sdr/bin/python")"
# Путь к headless скрипту
SCRIPT_PATH="${SCRIPT_PATH:-$SCRIPT_DIR/scripts_nn/data_saver_headless.py}"
SCRIPT_PATH="$(get_env_setting SCRIPT_PATH "$SCRIPT_DIR/scripts_nn/data_saver_headless.py")"
RUN_ONCE="${RUN_ONCE:-0}"
CAPTURE_LOG_FILE="${CAPTURE_LOG_FILE:-$BASE_DIR/capture_hourly.log}"
RUN_ONCE="$(get_env_setting RUN_ONCE "0")"
CAPTURE_LOG_FILE="$(get_env_setting CAPTURE_LOG_FILE "$BASE_DIR/capture_hourly.log")"
SYSTEMCTL_BIN=(systemctl)
CURRENT_CAPTURE_PID=""
CURRENT_SERVICE_UNIT=""
CURRENT_SERVICE_UNITS=()
@ -54,32 +111,32 @@ declare -A SERIAL
declare -A FREQ_HZ
declare -A SERVICE_UNIT
SERIAL[433]="$hack_433"
SERIAL[433]="$(get_env_setting hack_433)"
FREQ_HZ[433]="433000000"
SERIAL[750]="$hack_750"
SERIAL[750]="$(get_env_setting hack_750)"
FREQ_HZ[750]="750000000"
SERIAL[915]="$hack_915"
SERIAL[915]="$(get_env_setting hack_915)"
FREQ_HZ[915]="915000000"
SERIAL[1200]="$hack_1200"
SERIAL[1200]="$(get_env_setting hack_1200)"
FREQ_HZ[1200]="1200000000"
SERIAL[2400]="$hack_2400"
SERIAL[2400]="$(get_env_setting hack_2400)"
FREQ_HZ[2400]="2400000000"
SERIAL[3300]="$hack_3300"
SERIAL[3300]="$(get_env_setting hack_3300)"
FREQ_HZ[3300]="3300000000"
SERIAL[4500]="$hack_4500"
SERIAL[4500]="$(get_env_setting hack_4500)"
FREQ_HZ[4500]="4500000000"
SERIAL[5200]="$hack_5200"
SERIAL[5200]="$(get_env_setting hack_5200)"
FREQ_HZ[5200]="5200000000"
SERIAL[5800]="$hack_5800"
SERIAL[5800]="$(get_env_setting hack_5800)"
FREQ_HZ[5800]="5800000000"
SERVICE_UNIT[433]="dronedetector-sdr-433.service"
@ -109,33 +166,69 @@ service_exists() {
systemctl_run show "$unit" >/dev/null 2>&1
}
service_is_active() {
local unit="$1"
systemctl_run is-active --quiet "$unit"
}
remember_service_unit() {
local unit="$1"
local existing
for existing in "${CURRENT_SERVICE_UNITS[@]}"; do
if [[ "$existing" == "$unit" ]]; then
return 0
fi
done
CURRENT_SERVICE_UNITS+=("$unit")
}
stop_band_service() {
local band="$1"
local unit="${SERVICE_UNIT[$band]:-}"
local serial="${SERIAL[$band]:-}"
local other_band unit other_serial
if [[ -z "$unit" ]]; then
log "Для band=$band не найден service unit"
if [[ -z "$serial" ]]; then
log "Для band=$band пустой serial, пропускаю stop/start сервисов"
return 0
fi
if ! service_exists "$unit"; then
log "Service unit $unit не установлен, пропускаю stop/start"
return 0
fi
CURRENT_SERVICE_UNITS=()
for other_band in "${!SERVICE_UNIT[@]}"; do
unit="${SERVICE_UNIT[$other_band]:-}"
other_serial="${SERIAL[$other_band]:-}"
log "Останавливаю service $unit перед записью band=$band"
systemctl_run stop "$unit"
CURRENT_SERVICE_UNIT="$unit"
if [[ -z "$unit" || -z "$other_serial" || "$other_serial" != "$serial" ]]; then
continue
fi
if ! service_exists "$unit"; then
log "Service unit $unit не установлен, пропускаю"
continue
fi
if service_is_active "$unit"; then
remember_service_unit "$unit"
fi
log "Останавливаю service $unit перед записью band=$band"
systemctl_run stop "$unit"
done
}
start_current_service() {
if [[ -z "$CURRENT_SERVICE_UNIT" ]]; then
local unit
if [[ "${#CURRENT_SERVICE_UNITS[@]}" -eq 0 ]]; then
return 0
fi
log "Запускаю service $CURRENT_SERVICE_UNIT после записи"
systemctl_run start "$CURRENT_SERVICE_UNIT"
CURRENT_SERVICE_UNIT=""
for unit in "${CURRENT_SERVICE_UNITS[@]}"; do
log "Запускаю service $unit после записи"
systemctl_run start "$unit"
done
CURRENT_SERVICE_UNITS=()
}
cleanup_capture() {
@ -146,10 +239,13 @@ cleanup_capture() {
fi
CURRENT_CAPTURE_PID=""
if [[ -n "$CURRENT_SERVICE_UNIT" ]]; then
log "Восстанавливаю service $CURRENT_SERVICE_UNIT при завершении скрипта"
systemctl_run start "$CURRENT_SERVICE_UNIT" || true
CURRENT_SERVICE_UNIT=""
if [[ "${#CURRENT_SERVICE_UNITS[@]}" -gt 0 ]]; then
local unit
for unit in "${CURRENT_SERVICE_UNITS[@]}"; do
log "Восстанавливаю service $unit при завершении скрипта"
systemctl_run start "$unit" || true
done
CURRENT_SERVICE_UNITS=()
fi
}
@ -200,10 +296,13 @@ ensure_requirements() {
mkdir -p "$BASE_DIR"
mkdir -p "$(dirname "$CAPTURE_LOG_FILE")"
if [[ -n "${CAPTURE_ORDER:-}" ]]; then
local capture_order
capture_order="$(get_env_setting CAPTURE_ORDER)"
if [[ -n "$capture_order" ]]; then
ORDER=()
local band
for band in ${CAPTURE_ORDER//,/ }; do
for band in ${capture_order//,/ }; do
ORDER+=("$band")
done
fi

@ -1,3 +0,0 @@
SHARED_VECTOR_LEN = 4096
SHARED_868_ADDR = 'tcp://127.0.0.1:35068'
SHARED_915_ADDR = 'tcp://127.0.0.1:35069'

@ -97,6 +97,7 @@ services:
- ../../.env:/app/.env:ro
- ../../telemetry:/app/telemetry
- ../../common:/app/common
- ../../NN_server/result:/app/inference_result:ro
networks:
- dronedetector-net

@ -28,7 +28,7 @@ run_as_root() {
preflight() {
[[ -f "${PROJECT_ROOT}/deploy/requirements/nn_gpu_pinned.txt" ]] || die "Missing deploy/requirements/nn_gpu_pinned.txt"
[[ -f "${PROJECT_ROOT}/requirements-train.txt" ]] || die "Missing train_scripts/requirements-train.txt"
[[ -f "${PROJECT_ROOT}/train_scripts/requirements-train.txt" ]] || die "Missing train_scripts/requirements-train.txt"
[[ -f "${PROJECT_ROOT}/torchsig/pyproject.toml" ]] || die "Missing local torchsig package"
command -v "${PYTHON_BIN}" >/dev/null 2>&1 || die "${PYTHON_BIN} not found"
}

@ -21,7 +21,6 @@ server_port_2 = os.getenv('SERVER_PORT_2')
PARAMS = {'split_size': 400_000, 'point_amount': 100_000}
PARAMS['show_amount'] = 0.8 * PARAMS['point_amount']
token = 0
channel = 1
flag = 0
##############################
@ -30,6 +29,7 @@ flag = 0
f_base = 1.1e9
f_step = 20e6
f_roof = 1.3e9
channel = f_base
##############################
# Variables
##############################
@ -96,7 +96,7 @@ def work(lvl):
if f >= f_roof:
f = f_base
signal_arr = []
channel = 1
channel = f
return f, EOCF
else:
if flag == 0 and len(signal_arr) >= PARAMS['point_amount']:
@ -104,11 +104,11 @@ def work(lvl):
signal_arr = []
if flag == 0:
f += f_step
channel += 1
channel = f
if len(signal_arr) >= PARAMS['split_size']:
send_data(signal_arr[:PARAMS['split_size']])
flag = 0
signal_arr = []
channel += 1
f += f_step
channel = f
return f, EOCF

@ -22,7 +22,6 @@ server_port_2 = os.getenv('SERVER_PORT_2')
PARAMS = {'split_size': 400_000, 'point_amount': 100_000}
PARAMS['show_amount'] = 0.8 * PARAMS['point_amount']
token = 0
channel = 1
flag = 0
##############################
@ -31,6 +30,7 @@ flag = 0
f_base = 2.4e9
f_step = 20e6
f_roof = 2.5e9
channel = f_base
##############################
# Variables
##############################
@ -97,7 +97,7 @@ def work(lvl):
if f >= f_roof:
f = f_base
signal_arr = []
channel = 1
channel = f
return f, EOCF
else:
if flag == 0 and len(signal_arr) >= PARAMS['point_amount']:
@ -105,11 +105,11 @@ def work(lvl):
signal_arr = []
if flag == 0:
f += f_step
channel += 1
channel = f
if len(signal_arr) >= PARAMS['split_size']:
send_data(signal_arr[:PARAMS['split_size']])
flag = 0
signal_arr = []
channel += 1
channel = f
f += f_step
return f, EOCF

@ -22,7 +22,6 @@ server_port_2 = os.getenv('SERVER_PORT_2')
PARAMS = {'split_size': 400_000, 'point_amount': 100_000}
PARAMS['show_amount'] = 0.8 * PARAMS['point_amount']
token = 0
channel = 1
flag = 0
##############################
@ -31,6 +30,7 @@ flag = 0
f_base = 5.9e9
f_step = 20e6
f_roof = 5.7e9
channel = f_base
##############################
# Variables
##############################
@ -97,7 +97,7 @@ def work(lvl):
if f >= f_roof:
f = f_base
signal_arr = []
channel = 1
channel = f
return f, EOCF
else:
if flag == 0 and len(signal_arr) >= PARAMS['point_amount']:
@ -105,11 +105,11 @@ def work(lvl):
signal_arr = []
if flag == 0:
f += f_step
channel += 1
channel = f
if len(signal_arr) >= PARAMS['split_size']:
send_data(signal_arr[:PARAMS['split_size']])
flag = 0
signal_arr = []
channel += 1
channel = f
f += f_step
return f, EOCF

@ -21,12 +21,12 @@ server_port_2 = os.getenv('SERVER_PORT_2')
PARAMS = {'split_size': 400_000, 'point_amount': 100_000}
PARAMS['show_amount'] = int(0.8 * PARAMS['point_amount'])
token = 0
channel = 1
flag = 0
f_base = 0.91e9
f_step = 20e6
f_roof = 0.98e9
channel = f_base
f = f_base
EOCF = 0
@ -85,11 +85,11 @@ def advance_freq():
next_freq = f + f_step
if next_freq >= f_roof:
f = f_base
channel = 1
channel = f
return f, True
f = next_freq
channel += 1
channel = f
return f, False

@ -1,88 +1,168 @@
from gnuradio import blocks, gr, zeromq
import signal
from gnuradio import blocks, gr
import sys
import threading
import time
import signal
import compose_send_data_915 as my_freq
from common.runtime import load_root_env
from common.shared_stream_addrs import SHARED_915_ADDR, SHARED_VECTOR_LEN
import osmosdr
import time
import threading
import subprocess
import os
from common.runtime import load_root_env, resolve_hackrf_index
load_root_env(__file__)
def get_hack_id():
return resolve_hackrf_index('HACKID_915', 'orange_scripts/main_915.py')
serial_number = os.getenv('HACKID_1200')
pos = None
output = []
try:
command = 'lsusb -v -d 1d50:6089 | grep iSerial'
output.append(subprocess.check_output(command, shell=True, text=True))
except subprocess.CalledProcessError as e:
print(f"Команда завершилась с кодом возврата {e.returncode}")
print(e)
print(output)
output_lines = output[0].strip().split('\n')
print(output_lines)
serial_numbers = [line.split()[-1] for line in output_lines]
print(serial_numbers)
for i, number in enumerate(serial_numbers):
if number == serial_number:
id = i
break
if id is not None:
print('HackId is: {0}'.format(id))
return str(id)
else:
print('Такого хака нет!')
class get_center_freq(gr.top_block):
def __init__(self):
gr.top_block.__init__(self, 'get_center_freq')
self.prob_freq = 0
self.poll_rate = 10000
self.vector_len = SHARED_VECTOR_LEN
self.center_freq = 0
self.shared_addr = SHARED_915_ADDR
self._stop_polling = threading.Event()
self._prob_freq_thread = None
self.probSigVec = blocks.probe_signal_vc(self.vector_len)
self.shared_source_0 = zeromq.pull_source(
gr.sizeof_gr_complex,
self.vector_len,
self.shared_addr,
100,
False,
-1,
False,
gr.top_block.__init__(self, "get_center_freq")
##################################################
# Variables
##################################################
self.prob_freq = prob_freq = 0
self.top_peaks_amount = top_peaks_amount = 20
self.samp_rate = samp_rate = 20e6
self.poll_rate = poll_rate = 10000
self.num_points = num_points = 8192
self.flag = flag = 1
self.decimation = decimation = 1
self.center_freq = center_freq = my_freq.work(prob_freq)[0]
##################################################
# Blocks
##################################################
self.probSigVec = blocks.probe_signal_vc(4096)
self.rtlsdr_source_0 = osmosdr.source(
args="numchan=" + str(1) + " " + 'hackrf=' + get_hack_id()
)
self.connect((self.shared_source_0, 0), (self.probSigVec, 0))
def start_polling(self):
if self._prob_freq_thread is not None:
return
self.rtlsdr_source_0.set_time_unknown_pps(osmosdr.time_spec_t())
self.rtlsdr_source_0.set_sample_rate(samp_rate)
self.rtlsdr_source_0.set_center_freq(center_freq, 0)
self.rtlsdr_source_0.set_freq_corr(0, 0)
self.rtlsdr_source_0.set_gain(16, 0)
self.rtlsdr_source_0.set_if_gain(16, 0)
self.rtlsdr_source_0.set_bb_gain(0, 0)
self.rtlsdr_source_0.set_antenna('', 0)
self.rtlsdr_source_0.set_bandwidth(0, 0)
self.rtlsdr_source_0.set_min_output_buffer(4096)
def _prob_freq_probe():
while not self._stop_polling.is_set():
self.set_prob_freq(self.probSigVec.level())
time.sleep(1.0 / self.poll_rate)
while True:
val = self.probSigVec.level()
try:
self.set_prob_freq(val)
except AttributeError:
pass
time.sleep(1.0 / (poll_rate))
_prob_freq_thread = threading.Thread(target=_prob_freq_probe)
_prob_freq_thread.daemon = True
_prob_freq_thread.start()
self.blocks_stream_to_vector_1 = blocks.stream_to_vector(gr.sizeof_gr_complex*1, 4096)
self._prob_freq_thread = threading.Thread(target=_prob_freq_probe, daemon=True)
self._prob_freq_thread.start()
##################################################
# Connections
##################################################
self.connect((self.blocks_stream_to_vector_1, 0), (self.probSigVec, 0))
self.connect((self.rtlsdr_source_0, 0), (self.blocks_stream_to_vector_1, 0))
def get_prob_freq(self):
return self.prob_freq
def set_prob_freq(self, prob_freq):
self.prob_freq = prob_freq
self.center_freq = my_freq.work(self.prob_freq)[0]
self.set_center_freq(my_freq.work(self.prob_freq)[0])
def get_top_peaks_amount(self):
return self.top_peaks_amount
def set_top_peaks_amount(self, top_peaks_amount):
self.top_peaks_amount = top_peaks_amount
def get_samp_rate(self):
return self.samp_rate
def set_samp_rate(self, samp_rate):
self.samp_rate = samp_rate
self.rtlsdr_source_0.set_sample_rate(self.samp_rate)
def get_poll_rate(self):
return self.poll_rate
def set_poll_rate(self, poll_rate):
self.poll_rate = poll_rate
def get_num_points(self):
return self.num_points
def set_num_points(self, num_points):
self.num_points = num_points
def get_flag(self):
return self.flag
def set_flag(self, flag):
self.flag = flag
def get_decimation(self):
return self.decimation
def set_decimation(self, decimation):
self.decimation = decimation
def get_center_freq(self):
return self.center_freq
def set_center_freq(self, center_freq):
self.center_freq = center_freq
def close(self):
self._stop_polling.set()
self.stop()
self.wait()
self.rtlsdr_source_0.set_center_freq(self.center_freq, 0)
def main(top_block_cls=get_center_freq, options=None):
time.sleep(3)
tb = top_block_cls()
def sig_handler(sig=None, frame=None):
tb.close()
tb.stop()
tb.wait()
sys.exit(0)
signal.signal(signal.SIGINT, sig_handler)
signal.signal(signal.SIGTERM, sig_handler)
tb.start()
tb.start_polling()
try:
print('shared_pull_addr:', SHARED_915_ADDR)
input('Press Enter to quit: ')
except EOFError:
pass
tb.wait()

@ -24,6 +24,7 @@ def agregator(freq, alarm):
amplitude = 0
data = {"freq": freq,
#"channel": channel,
"amplitude": amplitude
}
return data

@ -2,10 +2,11 @@ import asyncio
import os
import time
from collections import defaultdict, deque
from pathlib import Path
from typing import Any, Deque, Dict, List, Optional
from fastapi import FastAPI, Query, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
from fastapi import FastAPI, HTTPException, Query, WebSocket, WebSocketDisconnect
from fastapi.responses import FileResponse, HTMLResponse
from pydantic import BaseModel, Field
from common.runtime import load_root_env
@ -16,15 +17,24 @@ TELEMETRY_BIND_HOST = os.getenv('telemetry_bind_host', os.getenv('lochost', '0.0
TELEMETRY_BIND_PORT = int(os.getenv('telemetry_bind_port', os.getenv('telemetry_port', '5020')))
TELEMETRY_HISTORY_SEC = int(float(os.getenv('telemetry_history_sec', '900')))
TELEMETRY_MAX_POINTS_PER_FREQ = int(os.getenv('telemetry_max_points_per_freq', '5000'))
INFERENCE_HISTORY_SEC = int(float(os.getenv('inference_history_sec', str(TELEMETRY_HISTORY_SEC))))
INFERENCE_MAX_RESULTS_PER_FREQ = int(os.getenv('inference_max_results_per_freq', '100'))
INFERENCE_RESULT_DIR = Path(os.getenv('inference_result_dir', '/app/inference_result')).resolve()
def _new_buffer() -> Deque[Dict[str, Any]]:
return deque(maxlen=TELEMETRY_MAX_POINTS_PER_FREQ)
def _new_inference_buffer() -> Deque[Dict[str, Any]]:
return deque(maxlen=INFERENCE_MAX_RESULTS_PER_FREQ)
app = FastAPI(title='DroneDetector Telemetry Server')
_buffers: Dict[str, Deque[Dict[str, Any]]] = defaultdict(_new_buffer)
_ws_clients: List[WebSocket] = []
_inference_buffers: Dict[str, Deque[Dict[str, Any]]] = defaultdict(_new_inference_buffer)
_inference_ws_clients: List[WebSocket] = []
_state_lock = asyncio.Lock()
@ -41,6 +51,18 @@ class TelemetryPoint(BaseModel):
alarm_channels: Optional[List[int]] = None
class InferenceResult(BaseModel):
result_id: int
ts: float = Field(default_factory=lambda: time.time())
freq: str
model: str
prediction: str
probability: float
drone_probability: Optional[float] = None
drone_threshold: Optional[str] = None
images: List[str] = Field(default_factory=list)
def _prune_freq_locked(freq: str, now_ts: float) -> None:
cutoff = now_ts - TELEMETRY_HISTORY_SEC
buf = _buffers[freq]
@ -62,6 +84,40 @@ def _copy_series_locked(seconds: int, freq: Optional[str] = None) -> Dict[str, L
return series
def _prune_inference_freq_locked(freq: str, now_ts: float) -> None:
cutoff = now_ts - INFERENCE_HISTORY_SEC
buf = _inference_buffers[freq]
while buf and float(buf[0].get('ts', 0.0)) < cutoff:
buf.popleft()
def _copy_inference_series_locked(limit: int, freq: Optional[str] = None) -> Dict[str, List[Dict[str, Any]]]:
now_ts = time.time()
cutoff = now_ts - INFERENCE_HISTORY_SEC
def _slice(buf: Deque[Dict[str, Any]]) -> List[Dict[str, Any]]:
recent = [item for item in buf if float(item.get('ts', 0.0)) >= cutoff]
return recent[-limit:]
if freq is not None:
return {freq: _slice(_inference_buffers.get(freq, deque()))}
series: Dict[str, List[Dict[str, Any]]] = {}
for key, buf in _inference_buffers.items():
series[key] = _slice(buf)
return series
def _sanitize_image_names(names: List[str]) -> List[str]:
safe_names: List[str] = []
for name in names:
base = Path(str(name)).name
if not base or not base.endswith('.png'):
continue
safe_names.append(base)
return safe_names
async def _broadcast(message: Dict[str, Any]) -> None:
dead: List[WebSocket] = []
for ws in list(_ws_clients):
@ -77,6 +133,21 @@ async def _broadcast(message: Dict[str, Any]) -> None:
_ws_clients.remove(ws)
async def _broadcast_inference(message: Dict[str, Any]) -> None:
dead: List[WebSocket] = []
for ws in list(_inference_ws_clients):
try:
await ws.send_json(message)
except Exception:
dead.append(ws)
if dead:
async with _state_lock:
for ws in dead:
if ws in _inference_ws_clients:
_inference_ws_clients.remove(ws)
@app.post('/telemetry')
async def ingest_telemetry(point: TelemetryPoint):
payload = point.model_dump()
@ -102,6 +173,31 @@ async def telemetry_history(
return {'seconds': seconds, 'series': series}
@app.post('/inference/result')
async def ingest_inference_result(result: InferenceResult):
payload = result.model_dump()
payload['images'] = _sanitize_image_names(payload.get('images', []))
freq = str(payload['freq'])
now_ts = time.time()
async with _state_lock:
_inference_buffers[freq].append(payload)
_prune_inference_freq_locked(freq, now_ts)
await _broadcast_inference({'type': 'inference_result', 'data': payload})
return {'ok': True}
@app.get('/inference/history')
async def inference_history(
freq: Optional[str] = Query(default=None),
limit: int = Query(default=20, ge=1, le=200),
):
async with _state_lock:
series = _copy_inference_series_locked(limit=limit, freq=freq)
return {'limit': limit, 'series': series}
@app.websocket('/telemetry/ws')
async def telemetry_ws(websocket: WebSocket):
await websocket.accept()
@ -123,6 +219,26 @@ async def telemetry_ws(websocket: WebSocket):
_ws_clients.remove(websocket)
@app.websocket('/inference/ws')
async def inference_ws(websocket: WebSocket):
await websocket.accept()
async with _state_lock:
_inference_ws_clients.append(websocket)
snapshot = _copy_inference_series_locked(limit=20, freq=None)
await websocket.send_json({'type': 'snapshot', 'data': snapshot})
try:
while True:
await websocket.receive_text()
except WebSocketDisconnect:
pass
finally:
async with _state_lock:
if websocket in _inference_ws_clients:
_inference_ws_clients.remove(websocket)
MONITOR_HTML = """
<!doctype html>
<html>
@ -453,12 +569,294 @@ loadInitial().then(connectWs).catch((e) => {
"""
INFERENCE_VIEWER_HTML = """
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DroneDetector Inference Viewer</title>
<style>
:root {
--bg: #f4f6f8;
--card: #ffffff;
--line: #d7dde5;
--text: #1c232e;
--muted: #647084;
--ok: #16a34a;
--warn: #b45309;
--accent: #0f6fff;
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--text); font-family: system-ui, -apple-system, Segoe UI, sans-serif; }
.wrap { max-width: 1800px; margin: 0 auto; padding: 16px; }
.head { display: flex; justify-content: space-between; align-items: center; gap: 16px; margin-bottom: 16px; }
.meta { color: var(--muted); font-size: 13px; }
.actions { display: flex; gap: 8px; align-items: center; }
button { border: 1px solid var(--line); background: var(--card); color: var(--text); border-radius: 8px; padding: 8px 12px; cursor: pointer; }
button.primary { background: var(--accent); color: #fff; border-color: var(--accent); }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(520px, 1fr)); gap: 14px; }
.card { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 14px; }
.card-head { display: flex; justify-content: space-between; align-items: start; gap: 12px; margin-bottom: 10px; }
.freq { font-size: 28px; font-weight: 700; }
.pill { display: inline-flex; align-items: center; gap: 6px; border-radius: 999px; padding: 4px 10px; font-size: 12px; }
.pill.live { background: #dcfce7; color: #166534; }
.pill.paused { background: #fef3c7; color: #92400e; }
.summary { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 12px; margin-bottom: 12px; }
.summary-item { border: 1px solid var(--line); border-radius: 10px; padding: 8px 10px; }
.summary-label { color: var(--muted); font-size: 12px; margin-bottom: 4px; }
.summary-value { font-size: 14px; font-weight: 600; word-break: break-word; }
.images { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-bottom: 12px; }
.image-card { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: #f8fafc; }
.image-card img { width: 100%; height: 180px; object-fit: contain; display: block; background: #fff; }
.image-card .cap { padding: 6px 8px; font-size: 12px; color: var(--muted); border-top: 1px solid var(--line); }
.history-title { font-size: 13px; font-weight: 600; margin-bottom: 8px; }
.history { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px; }
.thumb { min-width: 110px; max-width: 110px; border: 1px solid var(--line); border-radius: 10px; background: #fff; padding: 6px; cursor: pointer; }
.thumb.active { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.thumb img { width: 100%; height: 70px; object-fit: contain; display: block; background: #f8fafc; border-radius: 6px; }
.thumb .t1 { font-size: 12px; font-weight: 600; margin-top: 6px; }
.thumb .t2 { font-size: 11px; color: var(--muted); }
.empty { color: var(--muted); font-size: 13px; padding: 16px 0; }
</style>
</head>
<body>
<div class="wrap">
<div class="head">
<div>
<h2 style="margin:0 0 4px;">DroneDetector Inference Viewer</h2>
<div class="meta">Latest inference card per frequency. Browser keeps last 20 results per frequency.</div>
</div>
<div class="actions">
<div class="meta" id="ws-status">connecting...</div>
<button id="pause-btn" class="primary">Pause updates</button>
</div>
</div>
<div class="grid" id="cards"></div>
</div>
<script>
const MAX_RESULTS = 20;
const state = {};
const selected = {};
let paused = false;
let wsKeepalive = null;
function freqSort(a, b) { return Number(a) - Number(b); }
function formatTs(ts) {
return new Date(Number(ts) * 1000).toLocaleString('ru-RU', { hour12: false });
}
function escapeHtml(value) {
return String(value ?? '').replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;');
}
function imageUrl(name) {
return `/inference/images/${encodeURIComponent(name)}`;
}
function primaryImage(result) {
return (result.images || [])[0] || '';
}
function trimResults(freq) {
const arr = state[freq] || [];
state[freq] = arr.slice(-MAX_RESULTS);
}
function ensureCard(freq) {
if (document.getElementById(`card-${freq}`)) return;
const card = document.createElement('div');
card.className = 'card';
card.id = `card-${freq}`;
card.innerHTML = `
<div class="card-head">
<div class="freq">${escapeHtml(freq)} MHz</div>
<div class="pill ${paused ? 'paused' : 'live'}" id="badge-${freq}">${paused ? 'paused' : 'live'}</div>
</div>
<div class="summary" id="summary-${freq}"></div>
<div class="images" id="images-${freq}"></div>
<div class="history-title">Recent results</div>
<div class="history" id="history-${freq}"></div>
`;
document.getElementById('cards').appendChild(card);
}
function renderSummary(freq, result) {
const el = document.getElementById(`summary-${freq}`);
el.innerHTML = `
<div class="summary-item"><div class="summary-label">Model</div><div class="summary-value">${escapeHtml(result.model)}</div></div>
<div class="summary-item"><div class="summary-label">Prediction</div><div class="summary-value">${escapeHtml(result.prediction)}</div></div>
<div class="summary-item"><div class="summary-label">Confidence</div><div class="summary-value">${Number(result.probability).toFixed(3)}</div></div>
<div class="summary-item"><div class="summary-label">Time</div><div class="summary-value">${escapeHtml(formatTs(result.ts))}</div></div>
`;
}
function renderImages(freq, result) {
const el = document.getElementById(`images-${freq}`);
const images = result.images || [];
if (!images.length) {
el.innerHTML = '<div class="empty">No images for this inference.</div>';
return;
}
el.innerHTML = images.map((name) => `
<div class="image-card">
<img loading="lazy" src="${imageUrl(name)}" alt="${escapeHtml(name)}" />
<div class="cap">${escapeHtml(name)}</div>
</div>
`).join('');
}
function renderHistory(freq) {
const el = document.getElementById(`history-${freq}`);
const results = state[freq] || [];
if (!results.length) {
el.innerHTML = '<div class="empty">No results yet.</div>';
return;
}
el.innerHTML = results.slice().reverse().map((result) => {
const active = String(selected[freq]) === String(result.result_id) ? 'active' : '';
const image = primaryImage(result);
return `
<button class="thumb ${active}" data-freq="${escapeHtml(freq)}" data-result-id="${result.result_id}">
${image ? `<img loading="lazy" src="${imageUrl(image)}" alt="${escapeHtml(result.prediction)}" />` : '<div class="empty" style="padding:20px 0;">no image</div>'}
<div class="t1">${escapeHtml(result.prediction)}</div>
<div class="t2">p=${Number(result.probability).toFixed(2)}</div>
</button>
`;
}).join('');
el.querySelectorAll('.thumb').forEach((node) => {
node.addEventListener('click', () => {
selected[freq] = Number(node.dataset.resultId);
render(freq);
});
});
}
function render(freq) {
ensureCard(freq);
trimResults(freq);
const badge = document.getElementById(`badge-${freq}`);
badge.textContent = paused ? 'paused' : 'live';
badge.className = `pill ${paused ? 'paused' : 'live'}`;
const results = state[freq] || [];
if (!results.length) {
document.getElementById(`summary-${freq}`).innerHTML = '<div class="empty">No inference results yet.</div>';
document.getElementById(`images-${freq}`).innerHTML = '';
document.getElementById(`history-${freq}`).innerHTML = '';
return;
}
const active = results.find((item) => String(item.result_id) === String(selected[freq])) || results[results.length - 1];
selected[freq] = active.result_id;
renderSummary(freq, active);
renderImages(freq, active);
renderHistory(freq);
}
function renderAll() {
Object.keys(state).sort(freqSort).forEach(render);
}
function applySnapshot(series) {
const next = {};
for (const [freq, results] of Object.entries(series || {})) {
next[String(freq)] = Array.isArray(results) ? results.slice(-MAX_RESULTS) : [];
}
for (const freq of Object.keys(next)) {
state[freq] = next[freq];
if (state[freq].length) {
selected[freq] = state[freq][state[freq].length - 1].result_id;
}
}
renderAll();
}
function ingestResult(result) {
const freq = String(result.freq);
if (!state[freq]) state[freq] = [];
state[freq].push(result);
trimResults(freq);
selected[freq] = result.result_id;
render(freq);
}
async function loadHistory() {
const res = await fetch(`/inference/history?limit=${MAX_RESULTS}`);
const payload = await res.json();
applySnapshot(payload.series || {});
}
function setPaused(nextPaused) {
paused = nextPaused;
const btn = document.getElementById('pause-btn');
btn.textContent = paused ? 'Resume updates' : 'Pause updates';
btn.className = paused ? '' : 'primary';
renderAll();
}
function connectWs() {
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
const ws = new WebSocket(`${proto}://${location.host}/inference/ws`);
ws.onopen = () => {
document.getElementById('ws-status').textContent = paused ? 'ws connected (paused)' : 'ws connected';
if (wsKeepalive) clearInterval(wsKeepalive);
wsKeepalive = setInterval(() => {
if (ws.readyState === 1) ws.send('ping');
}, 20000);
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'snapshot' && msg.data) {
if (!paused) applySnapshot(msg.data);
return;
}
if (msg.type !== 'inference_result' || paused) return;
ingestResult(msg.data);
};
ws.onclose = () => {
document.getElementById('ws-status').textContent = 'ws disconnected, retrying...';
if (wsKeepalive) clearInterval(wsKeepalive);
setTimeout(connectWs, 1500);
};
ws.onerror = () => {
document.getElementById('ws-status').textContent = 'ws error';
};
}
document.getElementById('pause-btn').addEventListener('click', async () => {
if (paused) {
setPaused(false);
document.getElementById('ws-status').textContent = 'syncing...';
try {
await loadHistory();
document.getElementById('ws-status').textContent = 'ws connected';
} catch (err) {
document.getElementById('ws-status').textContent = `history error: ${err}`;
}
} else {
setPaused(true);
document.getElementById('ws-status').textContent = 'ws connected (paused)';
}
});
loadHistory().then(connectWs).catch((err) => {
document.getElementById('ws-status').textContent = `init error: ${err}`;
connectWs();
});
</script>
</body>
</html>
"""
@app.get('/', response_class=HTMLResponse)
@app.get('/monitor', response_class=HTMLResponse)
async def monitor_page():
return HTMLResponse(content=MONITOR_HTML)
@app.get('/inference-viewer', response_class=HTMLResponse)
async def inference_viewer_page():
return HTMLResponse(content=INFERENCE_VIEWER_HTML)
@app.get('/inference/images/{filename}')
async def inference_image(filename: str):
safe_name = Path(filename).name
if safe_name != filename:
raise HTTPException(status_code=404, detail='image not found')
image_path = (INFERENCE_RESULT_DIR / safe_name).resolve()
if image_path.parent != INFERENCE_RESULT_DIR or not image_path.is_file():
raise HTTPException(status_code=404, detail='image not found')
return FileResponse(image_path)
if __name__ == '__main__':
import uvicorn

Loading…
Cancel
Save