Compare commits

..

No commits in common. '9d7a0b4fdc216110a615c1918755a5cb42ddc8a1' and 'fed635a0ac77d9cbe457fdae99e33ab1fe769633' have entirely different histories.

@ -154,22 +154,18 @@ class Model(object):
except Exception as exc: except Exception as exc:
print(str(exc)) print(str(exc))
def _prepare_data(self, data=None, ind_inference=None): def _prepare_data(self, data=None):
try: try:
if ind_inference is None:
ind_inference = Model.get_ind_inference()
print('Подготовка данных' + self._shablon) print('Подготовка данных' + self._shablon)
self._data = self._pre_func(data, src=self._src_result, ind_inference=ind_inference) self._data = self._pre_func(data, src=self._src_result, ind_inference=Model.get_ind_inference())
except Exception as exc: except Exception as exc:
print(str(exc)) print(str(exc))
def _post_data(self, prediction=None, ind_inference=None): def _post_data(self, prediction=None):
if ind_inference is None:
ind_inference = Model.get_ind_inference()
print('Постобработка данных' + self._shablon) print('Постобработка данных' + self._shablon)
self._ind_inference += 1 self._ind_inference += 1
self._post_func(src=self._src_result, data=self._data, model_id=self._model_id, model_type=self._type_model, self._post_func(src=self._src_result, data=self._data, model_id=self._model_id, model_type=self._type_model,
ind_inference=ind_inference, prediction=prediction) ind_inference=Model.get_ind_inference(), prediction=prediction)
def get_test_inference(self): def get_test_inference(self):
try: try:
@ -252,25 +248,23 @@ class Model(object):
except Exception as exc: except Exception as exc:
print(str(exc)) print(str(exc))
def get_inference(self, data=None, ind_inference=None): def get_inference(self, data=None):
try: try:
return self._inference(data=data, ind_inference=ind_inference) return self._inference(data=data)
except Exception as exc: except Exception as exc:
print(str(exc)) print(str(exc))
return None return None
def _inference(self, data=None, ind_inference=None): def _inference(self, data=None):
try: try:
if ind_inference is None: Model._add_in_result_list(type_model=self._type_model, ind_inference=self.get_ind_inference(), list_to_add=[])
ind_inference = Model.get_ind_inference() self._prepare_data(data=data)
Model._add_in_result_list(type_model=self._type_model, ind_inference=ind_inference, list_to_add=[])
self._prepare_data(data=data, ind_inference=ind_inference)
print('Инференс' + self._shablon) print('Инференс' + self._shablon)
prediction, probability = self._inference_func(data=self._data, model=self._model, mapping=self._classes, prediction, probability = self._inference_func(data=self._data, model=self._model, mapping=self._classes,
shablon=self._shablon) shablon=self._shablon)
print('RESULT' + self._shablon + ': ' + str(prediction) + ' (probability=' + str(probability) + ')') print('RESULT' + self._shablon + ': ' + str(prediction) + ' (probability=' + str(probability) + ')')
Model._add_in_result_list(type_model=self._type_model, ind_inference=ind_inference, list_to_add=[prediction, probability]) Model._add_in_result_list(type_model=self._type_model, ind_inference=self.get_ind_inference(), list_to_add=[prediction, probability])
self._post_data(prediction=prediction, ind_inference=ind_inference) self._post_data(prediction=prediction)
gc.collect() gc.collect()
return prediction, probability return prediction, probability

@ -6,8 +6,6 @@ import torch
import cv2 import cv2
import gc import gc
import io import io
import os
import re
def _render_signal_channel(values, figsize=(16, 16), dpi=16, resize=(256, 256)): def _render_signal_channel(values, figsize=(16, 16), dpi=16, resize=(256, 256)):
@ -66,42 +64,6 @@ def _render_training_png(image):
return np.asarray(cv2.split(img), dtype=np.float32) return np.asarray(cv2.split(img), dtype=np.float32)
def _prune_old_inference_images(src, model_type, model_id, keep_last=200):
try:
keep_last = int(os.getenv("INFERENCE_IMAGE_KEEP_LAST", str(keep_last)))
except ValueError:
keep_last = keep_last
if keep_last <= 0 or not src or not os.path.isdir(src):
return
pattern = re.compile(
r"_inference_(\d+)_.*_"
+ re.escape(str(model_id))
+ "_"
+ re.escape(str(model_type))
+ r"\.png$"
)
grouped = {}
for name in os.listdir(src):
match = pattern.match(name)
if match is None:
continue
grouped.setdefault(int(match.group(1)), []).append(name)
if len(grouped) <= keep_last:
return
for old_result_id in sorted(grouped)[: len(grouped) - keep_last]:
for name in grouped[old_result_id]:
try:
os.remove(os.path.join(src, name))
except FileNotFoundError:
pass
except OSError as exc:
print(f"failed to remove old inference image {name}: {exc}")
def pre_func_ensemble(data=None, src="", ind_inference=0): def pre_func_ensemble(data=None, src="", ind_inference=0):
try: try:
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
@ -230,7 +192,7 @@ def post_func_ensemble(src="", model_type="", prediction="", model_id=0, ind_inf
matplotlib.use("Agg") matplotlib.use("Agg")
plt.ioff() plt.ioff()
if isinstance(data, (list, tuple)) and len(data) >= 2: if int(ind_inference) <= 100 and isinstance(data, (list, tuple)) and len(data) >= 2:
fig, ax = plt.subplots() fig, ax = plt.subplots()
ax.imshow(np.moveaxis(data[0], 0, -1)) ax.imshow(np.moveaxis(data[0], 0, -1))
plt.savefig(src + "_inference_" + str(ind_inference) + "_" + prediction + "_real_" + str(model_id) + "_" + model_type + ".png") plt.savefig(src + "_inference_" + str(ind_inference) + "_" + prediction + "_real_" + str(model_id) + "_" + model_type + ".png")
@ -249,8 +211,6 @@ def post_func_ensemble(src="", model_type="", prediction="", model_id=0, ind_inf
cv2.destroyAllWindows() cv2.destroyAllWindows()
gc.collect() gc.collect()
_prune_old_inference_images(src, model_type, model_id)
plt.clf() plt.clf()
plt.cla() plt.cla()
plt.close() plt.close()

@ -6,8 +6,6 @@ import torch
import cv2 import cv2
import gc import gc
import io import io
import os
import re
def _render_signal_channel(values, figsize=(16, 16), dpi=16, resize=(256, 256)): def _render_signal_channel(values, figsize=(16, 16), dpi=16, resize=(256, 256)):
@ -66,42 +64,6 @@ def _render_training_png(image):
return np.asarray(cv2.split(img), dtype=np.float32) return np.asarray(cv2.split(img), dtype=np.float32)
def _prune_old_inference_images(src, model_type, model_id, keep_last=200):
try:
keep_last = int(os.getenv("INFERENCE_IMAGE_KEEP_LAST", str(keep_last)))
except ValueError:
keep_last = keep_last
if keep_last <= 0 or not src or not os.path.isdir(src):
return
pattern = re.compile(
r"_inference_(\d+)_.*_"
+ re.escape(str(model_id))
+ "_"
+ re.escape(str(model_type))
+ r"\.png$"
)
grouped = {}
for name in os.listdir(src):
match = pattern.match(name)
if match is None:
continue
grouped.setdefault(int(match.group(1)), []).append(name)
if len(grouped) <= keep_last:
return
for old_result_id in sorted(grouped)[: len(grouped) - keep_last]:
for name in grouped[old_result_id]:
try:
os.remove(os.path.join(src, name))
except FileNotFoundError:
pass
except OSError as exc:
print(f"failed to remove old inference image {name}: {exc}")
def pre_func_ensemble(data=None, src="", ind_inference=0): def pre_func_ensemble(data=None, src="", ind_inference=0):
try: try:
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
@ -230,7 +192,7 @@ def post_func_ensemble(src="", model_type="", prediction="", model_id=0, ind_inf
matplotlib.use("Agg") matplotlib.use("Agg")
plt.ioff() plt.ioff()
if isinstance(data, (list, tuple)) and len(data) >= 2: if int(ind_inference) <= 100 and isinstance(data, (list, tuple)) and len(data) >= 2:
fig, ax = plt.subplots() fig, ax = plt.subplots()
ax.imshow(np.moveaxis(data[0], 0, -1)) ax.imshow(np.moveaxis(data[0], 0, -1))
plt.savefig(src + "_inference_" + str(ind_inference) + "_" + prediction + "_real_" + str(model_id) + "_" + model_type + ".png") plt.savefig(src + "_inference_" + str(ind_inference) + "_" + prediction + "_real_" + str(model_id) + "_" + model_type + ".png")
@ -249,8 +211,6 @@ def post_func_ensemble(src="", model_type="", prediction="", model_id=0, ind_inf
cv2.destroyAllWindows() cv2.destroyAllWindows()
gc.collect() gc.collect()
_prune_old_inference_images(src, model_type, model_id)
plt.clf() plt.clf()
plt.cla() plt.cla()
plt.close() plt.close()

@ -30,7 +30,6 @@ loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
queue = asyncio.Queue() queue = asyncio.Queue()
semaphore = asyncio.Semaphore(3) semaphore = asyncio.Semaphore(3)
receive_data_lock = threading.Lock()
prediction_list = [] prediction_list = []
result_msg = {} result_msg = {}
@ -98,8 +97,12 @@ def collect_inference_images(result_id, model_name=''):
if exact_images: if exact_images:
return result_id, exact_images return result_id, exact_images
if not grouped_images:
return result_id, [] return result_id, []
latest_result_id = max(grouped_images)
return latest_result_id, grouped_images[latest_result_id]
def send_inference_result(payload): def send_inference_result(payload):
try: try:
@ -177,11 +180,6 @@ def run_example():
@app.route('/receive_data', methods=['POST']) @app.route('/receive_data', methods=['POST'])
def receive_data(): def receive_data():
with receive_data_lock:
return _receive_data_locked()
def _receive_data_locked():
try: try:
print() print()
data = request.json data = request.json
@ -205,7 +203,7 @@ def _receive_data_locked():
print('-' * 100) print('-' * 100)
print(str(model)) print(str(model))
result_msg[str(model.get_model_name())] = {'freq': freq} 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)], ind_inference=result_id) 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())]['prediction'] = prediction
result_msg[str(model.get_model_name())]['probability'] = str(probability) result_msg[str(model.get_model_name())]['probability'] = str(probability)
prediction_list.append(prediction) prediction_list.append(prediction)

@ -1,59 +1,99 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_OWNER="${CAPTURE_USER:-$(stat -c %U "$SCRIPT_DIR")}"
SCRIPT_OWNER_HOME="$(getent passwd "$SCRIPT_OWNER" | cut -d: -f6 || true)"
cd "$SCRIPT_DIR"
############################ ############################
# НАСТРОЙКИ # НАСТРОЙКИ
############################ ############################
BASE_DIR="/mnt/data/dataset_6_5_26" # Точка сохранения, по умолчанию nvme/data.
BASE_DIR="${BASE_DIR:-/mnt/data/noise}"
PYTHON_BIN="${PYTHON_BIN:-$SCRIPT_DIR/.venv-sdr/bin/python}" # Путь к python из venv
SCRIPT_PATH="${SCRIPT_PATH:-$SCRIPT_DIR/scripts_nn/data_saver_headless.py}" PYTHON_BIN="${PYTHON_BIN:-$PWD/.venv-sdr/bin/python}"
ENV_FILE="${ENV_FILE:-$SCRIPT_DIR/.env}"
RUN_ONCE="${RUN_ONCE:-0}" # Путь к headless скрипту
CAPTURE_LOG_FILE="${CAPTURE_LOG_FILE:-$BASE_DIR/capture_hourly.log}" SCRIPT_PATH="${SCRIPT_PATH:-$PWD/scripts_nn/data_saver_headless.py}"
SYSTEMCTL_BIN=(systemctl) # Env-файл читается точечно: source .env небезопасен из-за сложных NN_* строк.
ENV_FILE="${ENV_FILE:-$PWD/.env}"
CURRENT_CAPTURE_PID=""
CURRENT_MOCK_PID=""
CURRENT_SERVICE_UNIT=""
STOPPED_SERVICE_UNIT=""
# Лимиты # Лимиты
PER_FREQ_LIMIT_GIB="${PER_FREQ_LIMIT_GIB:-12}" PER_FREQ_LIMIT_BYTES=$((3 * 1024 * 1024 * 1024)) # 3 GiB на частоту за запуск
TOTAL_LIMIT_GIB="${TOTAL_LIMIT_GIB:-266}" TOTAL_LIMIT_BYTES=$((128 * 1024 * 1024 * 1024)) # общий лимит 512 GiB
CYCLE_SECONDS="${CYCLE_SECONDS:-36}" # один цикл в час
PER_FREQ_LIMIT_BYTES=$((PER_FREQ_LIMIT_GIB * 1024 * 1024 * 1024))
TOTAL_LIMIT_BYTES=$((TOTAL_LIMIT_GIB * 1024 * 1024 * 1024))
# Для обычного hourly можно поставить 3600.
# Для RUN_ONCE это почти не важно.
CYCLE_SECONDS="${CYCLE_SECONDS:-1}"
# Параметры SDR # Параметры SDR
SAMP_RATE="${SAMP_RATE:-20e6}" SAMP_RATE="20e6"
SPLIT_SIZE="${SPLIT_SIZE:-400000}" SPLIT_SIZE="400000"
DELAY="${DELAY:-0.25}" DELAY="0.1"
RF_GAIN="${RF_GAIN:-12}" RF_GAIN="12"
IF_GAIN="${IF_GAIN:-12}" IF_GAIN="30"
BB_GAIN="${BB_GAIN:-0}" BB_GAIN="36"
############################ # Моки для server-to-master на время capture.
# MOCK SENDER
############################
CAPTURE_MOCK_SEND_ENABLED="${CAPTURE_MOCK_SEND_ENABLED:-1}" CAPTURE_MOCK_SEND_ENABLED="${CAPTURE_MOCK_SEND_ENABLED:-1}"
CAPTURE_MOCK_HOST="${CAPTURE_MOCK_HOST:-127.0.0.1}" CAPTURE_MOCK_HOST="${CAPTURE_MOCK_HOST:-127.0.0.1}"
CAPTURE_MOCK_INTERVAL_SECONDS="${CAPTURE_MOCK_INTERVAL_SECONDS:-1}" CAPTURE_MOCK_INTERVAL_SECONDS="${CAPTURE_MOCK_INTERVAL_SECONDS:-1}"
CAPTURE_MOCK_TIMEOUT_SECONDS="${CAPTURE_MOCK_TIMEOUT_SECONDS:-0.3}" CAPTURE_MOCK_TIMEOUT_SECONDS="${CAPTURE_MOCK_TIMEOUT_SECONDS:-0.3}"
CAPTURE_MOCK_LOG_SUCCESS="${CAPTURE_MOCK_LOG_SUCCESS:-0}" CAPTURE_MOCK_LOG_SUCCESS="${CAPTURE_MOCK_LOG_SUCCESS:-0}"
MOCK_SENDER_PID=""
ACTIVE_CAPTURE_PID=""
ACTIVE_MOCK_PID=""
ACTIVE_SERVICE_UNIT=""
SYSTEMCTL_BIN=(systemctl)
############################
# ЧАСТОТЫ И SERIAL ИЗ ENV
############################
#ORDER=(433 750 915 1200 2400 3300 4500 5200 5800)
ORDER=(1200)
declare -A SERIAL
declare -A FREQ_HZ
declare -A SERVICE_UNIT
#SERIAL[433]="$hack_433"
FREQ_HZ[433]="433000000"
#SERIAL[750]="$hack_750"
FREQ_HZ[750]="750000000"
#SERIAL[868]="$hack_868"
FREQ_HZ[868]="868000000"
#SERIAL[915]="$hack_915"
FREQ_HZ[915]="915000000"
SERIAL[1200]="0000000000000000114864dc38638a1b"
FREQ_HZ[1200]="1200000000"
#SERIAL[2400]="0000000000000000600463dc29789bc7"
FREQ_HZ[2400]="1200000000"
#SERIAL[3300]="$hack_3300"
FREQ_HZ[3300]="3300000000"
#SERIAL[4500]="$hack_4500"
FREQ_HZ[4500]="4500000000"
#SERIAL[5200]="$hack_5200"
FREQ_HZ[5200]="5200000000"
SERIAL[5800]="0000000000000000518864dc33743883"
FREQ_HZ[5800]="5800000000"
SERVICE_UNIT[433]="dronedetector-sdr-433.service"
SERVICE_UNIT[750]="dronedetector-sdr-750.service"
SERVICE_UNIT[868]="dronedetector-sdr-868.service"
SERVICE_UNIT[915]="dronedetector-sdr-915.service"
SERVICE_UNIT[1200]="dronedetector-sdr-1200.service"
SERVICE_UNIT[2400]="dronedetector-sdr-2400.service"
SERVICE_UNIT[3300]="dronedetector-sdr-3300.service"
SERVICE_UNIT[4500]="dronedetector-sdr-4500.service"
SERVICE_UNIT[5200]="dronedetector-sdr-5200.service"
SERVICE_UNIT[5800]="dronedetector-sdr-5800.service"
############################ ############################
# ВСПОМОГАТЕЛЬНОЕ # ВСПОМОГАТЕЛЬНОЕ
@ -67,6 +107,54 @@ systemctl_run() {
"${SYSTEMCTL_BIN[@]}" "$@" "${SYSTEMCTL_BIN[@]}" "$@"
} }
service_exists() {
local unit="$1"
systemctl_run show "$unit" >/dev/null 2>&1
}
service_is_active() {
local unit="$1"
systemctl_run is-active --quiet "$unit" >/dev/null 2>&1
}
stop_band_service() {
local band="$1"
local unit="${SERVICE_UNIT[$band]:-}"
ACTIVE_SERVICE_UNIT=""
if [[ -z "$unit" ]]; then
log "Для band=$band не найден service unit"
return 0
fi
if ! service_exists "$unit"; then
log "Service unit $unit не установлен, пропускаю stop/start"
return 0
fi
if ! service_is_active "$unit"; then
log "Service $unit не активен, stop/start не нужен"
return 0
fi
log "Останавливаю service $unit перед записью band=$band"
if ! systemctl_run stop "$unit"; then
log "Не удалось остановить service $unit"
return 1
fi
ACTIVE_SERVICE_UNIT="$unit"
}
start_active_service() {
if [[ -z "$ACTIVE_SERVICE_UNIT" ]]; then
return 0
fi
log "Запускаю service $ACTIVE_SERVICE_UNIT после записи"
systemctl_run start "$ACTIVE_SERVICE_UNIT" || log "Не удалось запустить service $ACTIVE_SERVICE_UNIT"
ACTIVE_SERVICE_UNIT=""
}
env_get() { env_get() {
local key="$1" local key="$1"
local default="${2:-}" local default="${2:-}"
@ -78,24 +166,19 @@ env_get() {
awk -v key="$key" -v default="$default" ' awk -v key="$key" -v default="$default" '
BEGIN { found = 0 } BEGIN { found = 0 }
$0 ~ "^[[:space:]]*" key "=" { $0 ~ "^[[:space:]]*" key "=" {
value = $0 value = $0
sub("^[[:space:]]*" key "=", "", value) sub("^[[:space:]]*" key "=", "", value)
sub("[[:space:]]+#.*$", "", value) sub("[[:space:]]+#.*$", "", value)
gsub("^[[:space:]]+|[[:space:]]+$", "", value) gsub("^[[:space:]]+|[[:space:]]+$", "", value)
if ((substr(value, 1, 1) == "\"" && substr(value, length(value), 1) == "\"") || if ((substr(value, 1, 1) == "\"" && substr(value, length(value), 1) == "\"") ||
(substr(value, 1, 1) == "'"'"'" && substr(value, length(value), 1) == "'"'"'")) { (substr(value, 1, 1) == "'"'"'" && substr(value, length(value), 1) == "'"'"'")) {
value = substr(value, 2, length(value) - 2) value = substr(value, 2, length(value) - 2)
} }
print value print value
found = 1 found = 1
exit exit
} }
END { END {
if (!found) { if (!found) {
print default print default
@ -113,145 +196,32 @@ mock_bool_enabled() {
mock_url() { mock_url() {
local port endpoint local port endpoint
port="${CAPTURE_MOCK_PORT:-$(env_get locport "$(env_get GENERAL_SERVER_PORT 5010)")}" port="${CAPTURE_MOCK_PORT:-$(env_get locport "$(env_get GENERAL_SERVER_PORT 5010)")}"
endpoint="${CAPTURE_MOCK_ENDPOINT:-$(env_get freq_endpoint process_data)}" endpoint="${CAPTURE_MOCK_ENDPOINT:-$(env_get freq_endpoint process_data)}"
endpoint="${endpoint#/}" endpoint="${endpoint#/}"
printf 'http://%s:%s/%s' "$CAPTURE_MOCK_HOST" "$port" "$endpoint" printf 'http://%s:%s/%s' "$CAPTURE_MOCK_HOST" "$port" "$endpoint"
} }
service_exists() {
local unit="$1"
systemctl_run show "$unit" >/dev/null 2>&1
}
service_is_active() {
local unit="$1"
systemctl_run is-active --quiet "$unit" >/dev/null 2>&1
}
dir_size_bytes() {
local path="$1"
if [[ -e "$path" ]]; then
du -sb "$path" 2>/dev/null | awk '{print $1}'
else
echo 0
fi
}
total_size_bytes() {
dir_size_bytes "$BASE_DIR"
}
terminate_pid() {
local pid="${1:-}"
if [[ -z "$pid" ]]; then
return 0
fi
if ! kill -0 "$pid" 2>/dev/null; then
return 0
fi
kill -TERM "$pid" 2>/dev/null || true
for _ in {1..10}; do
if ! kill -0 "$pid" 2>/dev/null; then
break
fi
sleep 1
done
if kill -0 "$pid" 2>/dev/null; then
log "PID=$pid не завершился по TERM, отправляю KILL"
kill -KILL "$pid" 2>/dev/null || true
fi
wait "$pid" 2>/dev/null || true
}
stop_band_service() {
local band="$1"
local unit="${SERVICE_UNIT[$band]:-}"
STOPPED_SERVICE_UNIT=""
CURRENT_SERVICE_UNIT=""
if [[ -z "$unit" ]]; then
log "Для band=$band не найден service unit"
return 0
fi
if ! service_exists "$unit"; then
log "Service unit $unit не установлен, пропускаю stop/start"
return 0
fi
log "Останавливаю service $unit перед записью band=$band"
if systemctl_run stop "$unit"; then
log "Service $unit остановлен или уже был остановлен"
else
log "Не удалось остановить service $unit"
fi
STOPPED_SERVICE_UNIT="$unit"
CURRENT_SERVICE_UNIT="$unit"
return 0
}
start_stopped_service() {
local unit="${STOPPED_SERVICE_UNIT:-}"
if [[ -z "$unit" ]]; then
return 0
fi
log "Запускаю service $unit после записи"
if systemctl_run start "$unit"; then
log "Service $unit запущен"
else
log "Не удалось запустить service $unit"
systemctl_run status "$unit" --no-pager || true
fi
STOPPED_SERVICE_UNIT=""
CURRENT_SERVICE_UNIT=""
}
start_mock_sender() { start_mock_sender() {
local band="$1" local band="$1"
local log_file="$2" local log_file="$2"
local url local url
CURRENT_MOCK_PID="" MOCK_SENDER_PID=""
if ! mock_bool_enabled "$CAPTURE_MOCK_SEND_ENABLED"; then if ! mock_bool_enabled "$CAPTURE_MOCK_SEND_ENABLED"; then
log "Mock sender отключен CAPTURE_MOCK_SEND_ENABLED=$CAPTURE_MOCK_SEND_ENABLED" log "Mock sender отключен CAPTURE_MOCK_SEND_ENABLED=$CAPTURE_MOCK_SEND_ENABLED"
return 0 return 0
fi fi
url="$(mock_url)" url="$(mock_url)"
log "Старт mock sender band=$band url=$url amplitude=0 interval=${CAPTURE_MOCK_INTERVAL_SECONDS}s" log "Старт mock sender band=$band url=$url amplitude=0 interval=${CAPTURE_MOCK_INTERVAL_SECONDS}s"
"$PYTHON_BIN" - \ "$PYTHON_BIN" - "$url" "$band" "$CAPTURE_MOCK_INTERVAL_SECONDS" "$CAPTURE_MOCK_TIMEOUT_SECONDS" <<'PY' >>"$log_file" 2>&1 &
"$url" \
"$band" \
"$CAPTURE_MOCK_INTERVAL_SECONDS" \
"$CAPTURE_MOCK_TIMEOUT_SECONDS" \
"$CAPTURE_MOCK_LOG_SUCCESS" \
>>"$log_file" 2>&1 <<'PY' &
import json import json
import os
import sys import sys
import time import time
import urllib.error
import urllib.request import urllib.request
@ -259,133 +229,61 @@ url = sys.argv[1]
freq = str(sys.argv[2]) freq = str(sys.argv[2])
interval = float(sys.argv[3]) interval = float(sys.argv[3])
timeout = float(sys.argv[4]) timeout = float(sys.argv[4])
log_success = sys.argv[5].lower() in {"1", "true", "yes", "on"}
payload = json.dumps({"freq": freq, "amplitude": 0}).encode("utf-8") payload = json.dumps({"freq": freq, "amplitude": 0}).encode("utf-8")
headers = {"Content-Type": "application/json"} headers = {"Content-Type": "application/json"}
log_success = os.getenv("CAPTURE_MOCK_LOG_SUCCESS", "0").lower() in {"1", "true", "yes", "on"}
print( print(f"[capture-mock] started url={url} freq={freq} amplitude=0 interval={interval}", flush=True)
f"[capture-mock] started url={url} freq={freq} amplitude=0 interval={interval}",
flush=True,
)
while True: while True:
try: try:
req = urllib.request.Request( req = urllib.request.Request(url, data=payload, headers=headers, method="POST")
url,
data=payload,
headers=headers,
method="POST",
)
with urllib.request.urlopen(req, timeout=timeout) as response: with urllib.request.urlopen(req, timeout=timeout) as response:
if log_success: if log_success:
print(f"[capture-mock] sent status={response.status}", flush=True) print(f"[capture-mock] sent status={response.status}", flush=True)
except Exception as exc: except Exception as exc:
print(f"[capture-mock] send failed: {exc}", flush=True) print(f"[capture-mock] send failed: {exc}", flush=True)
time.sleep(interval) time.sleep(interval)
PY PY
MOCK_SENDER_PID=$!
CURRENT_MOCK_PID=$! log "Mock sender PID=$MOCK_SENDER_PID"
log "Mock sender PID=$CURRENT_MOCK_PID"
} }
stop_mock_sender() { stop_mock_sender() {
local pid="${1:-}" local pid="${1:-}"
if [[ -z "$pid" ]]; then if [[ -z "$pid" ]]; then
return 0 return 0
fi fi
if kill -0 "$pid" 2>/dev/null; then if kill -0 "$pid" 2>/dev/null; then
log "Останавливаю mock sender PID=$pid" log "Останавливаю mock sender PID=$pid"
terminate_pid "$pid" kill -TERM "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
fi fi
} }
cleanup_capture() { cleanup_active_jobs() {
local rc=$? local rc=$?
if [[ -n "${ACTIVE_CAPTURE_PID:-}" ]] && kill -0 "$ACTIVE_CAPTURE_PID" 2>/dev/null; then
if [[ -n "${CURRENT_CAPTURE_PID:-}" ]] && kill -0 "$CURRENT_CAPTURE_PID" 2>/dev/null; then log "Останавливаю active capture PID=$ACTIVE_CAPTURE_PID"
log "Останавливаю текущий capture PID=$CURRENT_CAPTURE_PID" kill -TERM "$ACTIVE_CAPTURE_PID" 2>/dev/null || true
terminate_pid "$CURRENT_CAPTURE_PID" wait "$ACTIVE_CAPTURE_PID" 2>/dev/null || true
fi
CURRENT_CAPTURE_PID=""
if [[ -n "${CURRENT_MOCK_PID:-}" ]] && kill -0 "$CURRENT_MOCK_PID" 2>/dev/null; then
log "Останавливаю текущий mock sender PID=$CURRENT_MOCK_PID"
terminate_pid "$CURRENT_MOCK_PID"
fi fi
CURRENT_MOCK_PID="" stop_mock_sender "${ACTIVE_MOCK_PID:-}"
start_active_service
start_stopped_service
exit "$rc" exit "$rc"
} }
on_signal() { dir_size_bytes() {
local sig="$1" local path="$1"
log "Получен сигнал $sig, завершаю работу" if [[ -e "$path" ]]; then
exit 130 du -sb "$path" 2>/dev/null | awk '{print $1}'
else
echo 0
fi
} }
############################ total_size_bytes() {
# ЧАСТОТЫ И SERIAL dir_size_bytes "$BASE_DIR"
############################ }
ORDER=(2400)
declare -A SERIAL
declare -A FREQ_HZ
declare -A SERVICE_UNIT
SERIAL[433]="$(env_get hack_433)"
FREQ_HZ[433]="433000000"
SERIAL[750]="$(env_get hack_750)"
FREQ_HZ[750]="750000000"
SERIAL[868]="$(env_get hack_868)"
FREQ_HZ[868]="868000000"
SERIAL[915]="$(env_get hack_915)"
FREQ_HZ[915]="915000000"
SERIAL[1200]="$(env_get hack_1200)"
FREQ_HZ[1200]="1200000000"
SERIAL[2400]="$(env_get hack_2400)"
FREQ_HZ[2400]="2400000000"
SERIAL[3300]="$(env_get hack_3300)"
FREQ_HZ[3300]="3300000000"
SERIAL[4500]="$(env_get hack_4500)"
FREQ_HZ[4500]="4500000000"
SERIAL[5200]="$(env_get hack_5200)"
FREQ_HZ[5200]="5200000000"
SERIAL[5800]="$(env_get hack_5800)"
FREQ_HZ[5800]="5800000000"
SERVICE_UNIT[433]="dronedetector-sdr-433.service"
SERVICE_UNIT[750]="dronedetector-sdr-750.service"
SERVICE_UNIT[868]="dronedetector-sdr-868.service"
SERVICE_UNIT[915]="dronedetector-sdr-915.service"
SERVICE_UNIT[1200]="dronedetector-sdr-1200.service"
SERVICE_UNIT[2400]="dronedetector-sdr-2400.service"
SERVICE_UNIT[3300]="dronedetector-sdr-3300.service"
SERVICE_UNIT[4500]="dronedetector-sdr-4500.service"
SERVICE_UNIT[5200]="dronedetector-sdr-5200.service"
SERVICE_UNIT[5800]="dronedetector-sdr-5800.service"
############################
# ОСНОВНАЯ ЛОГИКА
############################
ensure_requirements() { ensure_requirements() {
if [[ ! -x "$PYTHON_BIN" ]]; then if [[ ! -x "$PYTHON_BIN" ]]; then
echo "Не найден python: $PYTHON_BIN" >&2 echo "Не найден python: $PYTHON_BIN" >&2
@ -412,63 +310,35 @@ ensure_requirements() {
fi fi
mkdir -p "$BASE_DIR" mkdir -p "$BASE_DIR"
mkdir -p "$(dirname "$CAPTURE_LOG_FILE")"
if [[ -n "${CAPTURE_ORDER:-}" ]]; then local dev
ORDER=() dev="$(df -P "$BASE_DIR" | awk 'NR==2 {print $1}')"
local band
for band in ${CAPTURE_ORDER//,/ }; do
ORDER+=("$band")
done
fi
if [[ "${#ORDER[@]}" -eq 0 ]]; then
echo "ORDER пустой. Укажите частоты в ORDER или задайте CAPTURE_ORDER." >&2
exit 1
fi
log "Рабочая директория: $SCRIPT_DIR"
log "BASE_DIR: $BASE_DIR"
log "ORDER: ${ORDER[*]}"
log "RUN_ONCE: $RUN_ONCE"
log "PER_FREQ_LIMIT_GIB: $PER_FREQ_LIMIT_GIB"
log "TOTAL_LIMIT_GIB: $TOTAL_LIMIT_GIB"
log "CYCLE_SECONDS: $CYCLE_SECONDS"
log "CAPTURE_MOCK_SEND_ENABLED: $CAPTURE_MOCK_SEND_ENABLED"
log "PYTHON_BIN: $PYTHON_BIN"
log "SCRIPT_PATH: $SCRIPT_PATH"
} }
run_one_freq() { run_one_freq() {
local band="$1" local band="$1"
local serial="${SERIAL[$band]:-}" local serial="${SERIAL[$band]}"
local freq="${FREQ_HZ[$band]:-}" local freq="${FREQ_HZ[$band]}"
if [[ -z "$serial" ]]; then if [[ -z "$serial" ]]; then
log "Для band=$band пустой serial, пропускаю" log "Для band=$band пустой serial, пропускаю"
return 0 return 0
fi fi
if [[ -z "$freq" ]]; then
log "Для band=$band не задана частота, пропускаю"
return 0
fi
local ts out_dir log_file local ts out_dir log_file
ts="$(date '+%F_%H-%M-%S')" ts="$(date '+%F_%H-%M-%S')"
out_dir="$BASE_DIR/$band/$ts" out_dir="$BASE_DIR/$band/$ts"
log_file="$out_dir/run.log" log_file="$out_dir/run.log"
log "Старт band=$band serial=$serial freq=$freq dir=$out_dir" mkdir -p "$out_dir"
if ! mkdir -p "$out_dir"; then log "Старт band=$band serial=$serial freq=$freq dir=$out_dir"
log "Не удалось создать каталог $out_dir" if ! stop_band_service "$band"; then
return 1 return 1
fi fi
stop_band_service "$band"
"$PYTHON_BIN" "$SCRIPT_PATH" \ "$PYTHON_BIN" "$SCRIPT_PATH" \
--serial "$serial" \ --serial "$serial" \
--freq "$freq" \ --freq "$freq" \
@ -483,72 +353,77 @@ run_one_freq() {
>"$log_file" 2>&1 & >"$log_file" 2>&1 &
local pid=$! local pid=$!
CURRENT_CAPTURE_PID="$pid" local mock_pid=""
log "Capture PID=$pid" log "PID=$pid"
ACTIVE_CAPTURE_PID="$pid"
start_mock_sender "$band" "$log_file" start_mock_sender "$band" "$log_file"
local mock_pid="$CURRENT_MOCK_PID" mock_pid="$MOCK_SENDER_PID"
ACTIVE_MOCK_PID="$mock_pid"
while kill -0 "$pid" 2>/dev/null; do while kill -0 "$pid" 2>/dev/null; do
local cur_dir_size cur_total_size local cur_dir_size cur_total_size
cur_dir_size="$(dir_size_bytes "$out_dir")" cur_dir_size="$(dir_size_bytes "$out_dir")"
cur_total_size="$(total_size_bytes)" cur_total_size="$(total_size_bytes)"
if (( cur_total_size >= TOTAL_LIMIT_BYTES )); then if (( cur_total_size >= TOTAL_LIMIT_BYTES )); then
log "Достигнут общий лимит ${TOTAL_LIMIT_GIB} GiB. Останавливаю PID=$pid" log "Достигнут общий лимит 512 GiB. Останавливаю PID=$pid"
kill -TERM "$pid" 2>/dev/null || true
terminate_pid "$pid" wait "$pid" 2>/dev/null || true
CURRENT_CAPTURE_PID=""
stop_mock_sender "$mock_pid" stop_mock_sender "$mock_pid"
CURRENT_MOCK_PID="" start_active_service
ACTIVE_CAPTURE_PID=""
start_stopped_service ACTIVE_MOCK_PID=""
return 2 return 2
fi fi
if (( cur_dir_size >= PER_FREQ_LIMIT_BYTES )); then if (( cur_dir_size >= PER_FREQ_LIMIT_BYTES )); then
log "Для band=$band достигнут лимит ${PER_FREQ_LIMIT_GIB} GiB. Останавливаю PID=$pid" log "Для band=$band достигнут лимит 3 GiB. Останавливаю PID=$pid"
kill -TERM "$pid" 2>/dev/null || true
terminate_pid "$pid"
CURRENT_CAPTURE_PID=""
for _ in {1..10}; do
if ! kill -0 "$pid" 2>/dev/null; then
break break
fi fi
sleep 1 sleep 1
done done
wait "$pid" 2>/dev/null || true if kill -0 "$pid" 2>/dev/null; then
CURRENT_CAPTURE_PID="" log "PID=$pid не завершился по TERM, отправляю KILL"
kill -KILL "$pid" 2>/dev/null || true
fi
wait "$pid" 2>/dev/null || true
stop_mock_sender "$mock_pid" stop_mock_sender "$mock_pid"
CURRENT_MOCK_PID="" start_active_service
ACTIVE_CAPTURE_PID=""
ACTIVE_MOCK_PID=""
break
fi
start_stopped_service sleep 1
done
stop_mock_sender "$mock_pid"
start_active_service
ACTIVE_CAPTURE_PID=""
ACTIVE_MOCK_PID=""
log "Завершен band=$band, размер=$(du -sh "$out_dir" | awk '{print $1}')" log "Завершен band=$band, размер=$(du -sh "$out_dir" | awk '{print $1}')"
return 0 return 0
} }
main_loop() { main_loop() {
while true; do while true; do
local total_before cycle_start elapsed sleep_left local total_before cycle_start elapsed sleep_left
total_before="$(total_size_bytes)" total_before="$(total_size_bytes)"
if (( total_before >= TOTAL_LIMIT_BYTES )); then if (( total_before >= TOTAL_LIMIT_BYTES )); then
log "Общий размер уже >= ${TOTAL_LIMIT_GIB} GiB, выхожу" log "Общий размер уже >= 512 GiB, выхожу"
break break
fi fi
cycle_start="$(date +%s)" cycle_start="$(date +%s)"
log "Новый цикл" log "Новый цикл"
local band
for band in "${ORDER[@]}"; do for band in "${ORDER[@]}"; do
if (( $(total_size_bytes) >= TOTAL_LIMIT_BYTES )); then if (( $(total_size_bytes) >= TOTAL_LIMIT_BYTES )); then
log "Общий лимит достигнут внутри цикла, выхожу" log "Общий лимит достигнут внутри цикла, выхожу"
@ -556,28 +431,19 @@ main_loop() {
fi fi
run_one_freq "$band" || { run_one_freq "$band" || {
local rc=$? rc=$?
if [[ $rc -eq 2 ]]; then
if [[ "$rc" -eq 2 ]]; then
log "Остановка по общему лимиту" log "Остановка по общему лимиту"
return 0 return 0
fi fi
log "Ошибка записи band=$band rc=$rc"
return "$rc"
} }
done done
if [[ "$RUN_ONCE" == "1" ]]; then
log "RUN_ONCE=1, завершаю работу после одного цикла"
break
fi
elapsed=$(( $(date +%s) - cycle_start )) elapsed=$(( $(date +%s) - cycle_start ))
sleep_left=$(( CYCLE_SECONDS - elapsed )) sleep_left=$(( CYCLE_SECONDS - elapsed ))
if (( sleep_left > 0 )); then if (( sleep_left > 0 )); then
log "Цикл занял ${elapsed} сек, жду ${sleep_left} сек" log "Цикл занял ${elapsed} сек, жду ${sleep_left} сек до следующего часа"
sleep "$sleep_left" sleep "$sleep_left"
else else
log "Цикл занял ${elapsed} сек, паузы нет" log "Цикл занял ${elapsed} сек, паузы нет"
@ -585,9 +451,8 @@ main_loop() {
done done
} }
trap cleanup_capture EXIT if [[ "${CAPTURE_HOURLY_SOURCE_ONLY:-0}" != "1" ]]; then
trap 'on_signal INT' INT trap cleanup_active_jobs INT TERM
trap 'on_signal TERM' TERM ensure_requirements
main_loop
ensure_requirements fi
main_loop

@ -67,8 +67,8 @@ class get_center_freq(gr.top_block):
self.rtlsdr_source_0.set_sample_rate(samp_rate) self.rtlsdr_source_0.set_sample_rate(samp_rate)
self.rtlsdr_source_0.set_center_freq(center_freq, 0) self.rtlsdr_source_0.set_center_freq(center_freq, 0)
self.rtlsdr_source_0.set_freq_corr(0, 0) self.rtlsdr_source_0.set_freq_corr(0, 0)
self.rtlsdr_source_0.set_gain(0, 0) self.rtlsdr_source_0.set_gain(16, 0)
self.rtlsdr_source_0.set_if_gain(0, 0) self.rtlsdr_source_0.set_if_gain(16, 0)
self.rtlsdr_source_0.set_bb_gain(0, 0) self.rtlsdr_source_0.set_bb_gain(0, 0)
self.rtlsdr_source_0.set_antenna('', 0) self.rtlsdr_source_0.set_antenna('', 0)
self.rtlsdr_source_0.set_bandwidth(0, 0) self.rtlsdr_source_0.set_bandwidth(0, 0)

@ -40,8 +40,8 @@ class get_center_freq(gr.top_block):
self.rtlsdr_source_0.set_sample_rate(self.samp_rate) self.rtlsdr_source_0.set_sample_rate(self.samp_rate)
self.rtlsdr_source_0.set_center_freq(self.center_freq, 0) self.rtlsdr_source_0.set_center_freq(self.center_freq, 0)
self.rtlsdr_source_0.set_freq_corr(0, 0) self.rtlsdr_source_0.set_freq_corr(0, 0)
self.rtlsdr_source_0.set_gain(0, 0) self.rtlsdr_source_0.set_gain(24, 0)
self.rtlsdr_source_0.set_if_gain(0, 0) self.rtlsdr_source_0.set_if_gain(24, 0)
self.rtlsdr_source_0.set_bb_gain(100, 0) self.rtlsdr_source_0.set_bb_gain(100, 0)
self.rtlsdr_source_0.set_antenna('', 0) self.rtlsdr_source_0.set_antenna('', 0)
self.rtlsdr_source_0.set_bandwidth(0, 0) self.rtlsdr_source_0.set_bandwidth(0, 0)

@ -144,10 +144,9 @@ def _resolve_latest_images_for_model(payload: Dict[str, Any]) -> Dict[str, Any]:
return payload return payload
current_id = int(payload.get('result_id', 0) or 0) current_id = int(payload.get('result_id', 0) or 0)
if current_id in grouped: resolved_id = current_id if current_id in grouped else max(grouped)
payload['images'] = sorted(grouped[current_id]) payload['result_id'] = resolved_id
else: payload['images'] = sorted(grouped[resolved_id])
payload['images'] = _sanitize_image_names(payload.get('images', []))
return payload return payload

Loading…
Cancel
Save