diff --git a/capture_hourly.sh b/capture_hourly.sh index e45dae0..83ebe7c 100755 --- a/capture_hourly.sh +++ b/capture_hourly.sh @@ -5,8 +5,8 @@ set -Eeuo pipefail # НАСТРОЙКИ ############################ -# ЗАМЕНИ на реальную точку монтирования nvme1n1 -BASE_DIR="/mnt/data/noise" +# Точка сохранения, по умолчанию nvme/data. +BASE_DIR="${BASE_DIR:-/mnt/data/noise}" # Путь к python из venv PYTHON_BIN="${PYTHON_BIN:-$PWD/.venv-sdr/bin/python}" @@ -14,11 +14,14 @@ PYTHON_BIN="${PYTHON_BIN:-$PWD/.venv-sdr/bin/python}" # Путь к headless скрипту SCRIPT_PATH="${SCRIPT_PATH:-$PWD/scripts_nn/data_saver_headless.py}" +# Env-файл читается точечно: source .env небезопасен из-за сложных NN_* строк. +ENV_FILE="${ENV_FILE:-$PWD/.env}" + # Лимиты PER_FREQ_LIMIT_BYTES=$((3 * 1024 * 1024 * 1024)) # 3 GiB на частоту за запуск TOTAL_LIMIT_BYTES=$((128 * 1024 * 1024 * 1024)) # общий лимит 512 GiB -CYCLE_SECONDS=36 # один цикл в час +CYCLE_SECONDS="${CYCLE_SECONDS:-36}" # один цикл в час # Параметры SDR SAMP_RATE="20e6" @@ -28,6 +31,16 @@ RF_GAIN="12" IF_GAIN="30" BB_GAIN="36" +# Моки для server-to-master на время capture. +CAPTURE_MOCK_SEND_ENABLED="${CAPTURE_MOCK_SEND_ENABLED:-1}" +CAPTURE_MOCK_HOST="${CAPTURE_MOCK_HOST:-127.0.0.1}" +CAPTURE_MOCK_INTERVAL_SECONDS="${CAPTURE_MOCK_INTERVAL_SECONDS:-1}" +CAPTURE_MOCK_TIMEOUT_SECONDS="${CAPTURE_MOCK_TIMEOUT_SECONDS:-0.3}" +CAPTURE_MOCK_LOG_SUCCESS="${CAPTURE_MOCK_LOG_SUCCESS:-0}" +MOCK_SENDER_PID="" +ACTIVE_CAPTURE_PID="" +ACTIVE_MOCK_PID="" + ############################ # ЧАСТОТЫ И SERIAL ИЗ ENV ############################ @@ -76,6 +89,122 @@ log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*" } +env_get() { + local key="$1" + local default="${2:-}" + + if [[ ! -f "$ENV_FILE" ]]; then + printf '%s\n' "$default" + return 0 + fi + + awk -v key="$key" -v default="$default" ' + BEGIN { found = 0 } + $0 ~ "^[[:space:]]*" key "=" { + value = $0 + sub("^[[:space:]]*" key "=", "", value) + sub("[[:space:]]+#.*$", "", value) + gsub("^[[:space:]]+|[[:space:]]+$", "", value) + if ((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) + } + print value + found = 1 + exit + } + END { + if (!found) { + print default + } + } + ' "$ENV_FILE" +} + +mock_bool_enabled() { + case "${1,,}" in + 1|true|yes|on) return 0 ;; + *) return 1 ;; + esac +} + +mock_url() { + local port endpoint + port="${CAPTURE_MOCK_PORT:-$(env_get locport "$(env_get GENERAL_SERVER_PORT 5010)")}" + endpoint="${CAPTURE_MOCK_ENDPOINT:-$(env_get freq_endpoint process_data)}" + endpoint="${endpoint#/}" + printf 'http://%s:%s/%s' "$CAPTURE_MOCK_HOST" "$port" "$endpoint" +} + +start_mock_sender() { + local band="$1" + local log_file="$2" + local url + + MOCK_SENDER_PID="" + if ! mock_bool_enabled "$CAPTURE_MOCK_SEND_ENABLED"; then + log "Mock sender отключен CAPTURE_MOCK_SEND_ENABLED=$CAPTURE_MOCK_SEND_ENABLED" + return 0 + fi + + url="$(mock_url)" + log "Старт mock sender band=$band url=$url amplitude=0 interval=${CAPTURE_MOCK_INTERVAL_SECONDS}s" + + "$PYTHON_BIN" - "$url" "$band" "$CAPTURE_MOCK_INTERVAL_SECONDS" "$CAPTURE_MOCK_TIMEOUT_SECONDS" <<'PY' >>"$log_file" 2>&1 & +import json +import os +import sys +import time +import urllib.error +import urllib.request + + +url = sys.argv[1] +freq = str(sys.argv[2]) +interval = float(sys.argv[3]) +timeout = float(sys.argv[4]) +payload = json.dumps({"freq": freq, "amplitude": 0}).encode("utf-8") +headers = {"Content-Type": "application/json"} +log_success = os.getenv("CAPTURE_MOCK_LOG_SUCCESS", "0").lower() in {"1", "true", "yes", "on"} + +print(f"[capture-mock] started url={url} freq={freq} amplitude=0 interval={interval}", flush=True) +while True: + try: + req = urllib.request.Request(url, data=payload, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=timeout) as response: + if log_success: + print(f"[capture-mock] sent status={response.status}", flush=True) + except Exception as exc: + print(f"[capture-mock] send failed: {exc}", flush=True) + time.sleep(interval) +PY + MOCK_SENDER_PID=$! + log "Mock sender PID=$MOCK_SENDER_PID" +} + +stop_mock_sender() { + local pid="${1:-}" + if [[ -z "$pid" ]]; then + return 0 + fi + if kill -0 "$pid" 2>/dev/null; then + log "Останавливаю mock sender PID=$pid" + kill -TERM "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true + fi +} + +cleanup_active_jobs() { + local rc=$? + if [[ -n "${ACTIVE_CAPTURE_PID:-}" ]] && kill -0 "$ACTIVE_CAPTURE_PID" 2>/dev/null; then + log "Останавливаю active capture PID=$ACTIVE_CAPTURE_PID" + kill -TERM "$ACTIVE_CAPTURE_PID" 2>/dev/null || true + wait "$ACTIVE_CAPTURE_PID" 2>/dev/null || true + fi + stop_mock_sender "${ACTIVE_MOCK_PID:-}" + exit "$rc" +} + dir_size_bytes() { local path="$1" if [[ -e "$path" ]]; then @@ -135,7 +264,12 @@ run_one_freq() { >"$log_file" 2>&1 & local pid=$! + local mock_pid="" log "PID=$pid" + ACTIVE_CAPTURE_PID="$pid" + start_mock_sender "$band" "$log_file" + mock_pid="$MOCK_SENDER_PID" + ACTIVE_MOCK_PID="$mock_pid" while kill -0 "$pid" 2>/dev/null; do local cur_dir_size cur_total_size @@ -146,6 +280,9 @@ run_one_freq() { log "Достигнут общий лимит 512 GiB. Останавливаю PID=$pid" kill -TERM "$pid" 2>/dev/null || true wait "$pid" 2>/dev/null || true + stop_mock_sender "$mock_pid" + ACTIVE_CAPTURE_PID="" + ACTIVE_MOCK_PID="" return 2 fi @@ -166,12 +303,18 @@ run_one_freq() { fi wait "$pid" 2>/dev/null || true + stop_mock_sender "$mock_pid" + ACTIVE_CAPTURE_PID="" + ACTIVE_MOCK_PID="" break fi sleep 1 done + stop_mock_sender "$mock_pid" + ACTIVE_CAPTURE_PID="" + ACTIVE_MOCK_PID="" log "Завершен band=$band, размер=$(du -sh "$out_dir" | awk '{print $1}')" return 0 } @@ -216,5 +359,8 @@ main_loop() { done } -ensure_requirements -main_loop \ No newline at end of file +if [[ "${CAPTURE_HOURLY_SOURCE_ONLY:-0}" != "1" ]]; then + trap cleanup_active_jobs INT TERM + ensure_requirements + main_loop +fi