You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DroneDetector/capture_hourly.sh

459 lines
11 KiB
Bash

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/usr/bin/env bash
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)"
cd "$SCRIPT_DIR"
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="$(get_env_setting CAPTURE_BASE_DIR "${SCRIPT_OWNER_HOME}/dataset/noise")"
# Путь к python из venv
PYTHON_BIN="$(get_env_setting PYTHON_BIN "$SCRIPT_DIR/.venv-sdr/bin/python")"
# Путь к headless скрипту
SCRIPT_PATH="$(get_env_setting SCRIPT_PATH "$SCRIPT_DIR/scripts_nn/data_saver_headless.py")"
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_UNITS=()
# Лимиты
lim_all=10
PER_FREQ_LIMIT_BYTES=$((1 * 1024 * 1024 * 1024)) # GiB на частоту за запуск
TOTAL_LIMIT_BYTES=$((lim_all * 1024 * 1024 * 1024)) # общий лимит GiB
CYCLE_SECONDS=1 # один цикл в час
# Параметры SDR
SAMP_RATE="20e6"
SPLIT_SIZE="400000"
DELAY="0.01"
RF_GAIN="12"
IF_GAIN="30"
BB_GAIN="36"
############################
# ЧАСТОТЫ И SERIAL ИЗ ENV
############################
ORDER=(433)
declare -A SERIAL
declare -A FREQ_HZ
declare -A SERVICE_UNIT
SERIAL[433]="$(get_env_setting hack_433)"
FREQ_HZ[433]="433000000"
SERIAL[750]="$(get_env_setting hack_750)"
FREQ_HZ[750]="750000000"
SERIAL[915]="$(get_env_setting hack_915)"
FREQ_HZ[915]="915000000"
SERIAL[1200]="$(get_env_setting hack_1200)"
FREQ_HZ[1200]="1200000000"
SERIAL[2400]="$(get_env_setting hack_2400)"
FREQ_HZ[2400]="2400000000"
SERIAL[3300]="$(get_env_setting hack_3300)"
FREQ_HZ[3300]="3300000000"
SERIAL[4500]="$(get_env_setting hack_4500)"
FREQ_HZ[4500]="4500000000"
SERIAL[5200]="$(get_env_setting hack_5200)"
FREQ_HZ[5200]="5200000000"
SERIAL[5800]="$(get_env_setting hack_5800)"
FREQ_HZ[5800]="5800000000"
SERVICE_UNIT[433]="dronedetector-sdr-433.service"
SERVICE_UNIT[750]="dronedetector-sdr-750.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"
############################
# ВСПОМОГАТЕЛЬНОЕ
############################
log() {
printf '[%s] %s\n' "$(date '+%F %T')" "$*"
}
systemctl_run() {
"${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"
}
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 serial="${SERIAL[$band]:-}"
local other_band unit other_serial
if [[ -z "$serial" ]]; then
log "Для band=$band пустой serial, пропускаю 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]:-}"
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() {
local unit
if [[ "${#CURRENT_SERVICE_UNITS[@]}" -eq 0 ]]; then
return 0
fi
for unit in "${CURRENT_SERVICE_UNITS[@]}"; do
log "Запускаю service $unit после записи"
systemctl_run start "$unit"
done
CURRENT_SERVICE_UNITS=()
}
cleanup_capture() {
if [[ -n "$CURRENT_CAPTURE_PID" ]] && kill -0 "$CURRENT_CAPTURE_PID" 2>/dev/null; then
log "Останавливаю текущий PID=$CURRENT_CAPTURE_PID при завершении скрипта"
kill -TERM "$CURRENT_CAPTURE_PID" 2>/dev/null || true
wait "$CURRENT_CAPTURE_PID" 2>/dev/null || true
fi
CURRENT_CAPTURE_PID=""
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
}
on_signal() {
local sig="$1"
log "Получен сигнал $sig, завершаю работу"
exit 130
}
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"
}
ensure_requirements() {
if [[ ! -x "$PYTHON_BIN" ]]; then
echo "Не найден python: $PYTHON_BIN" >&2
exit 1
fi
if [[ ! -f "$SCRIPT_PATH" ]]; then
echo "Не найден script: $SCRIPT_PATH" >&2
exit 1
fi
if ! command -v systemctl >/dev/null 2>&1; then
echo "Не найден systemctl" >&2
exit 1
fi
if [[ "${EUID}" -ne 0 ]]; then
if command -v sudo >/dev/null 2>&1; then
SYSTEMCTL_BIN=(sudo systemctl)
else
echo "Для управления service нужен root или sudo" >&2
exit 1
fi
fi
mkdir -p "$BASE_DIR"
mkdir -p "$(dirname "$CAPTURE_LOG_FILE")"
local capture_order
capture_order="$(get_env_setting CAPTURE_ORDER)"
if [[ -n "$capture_order" ]]; then
ORDER=()
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"
}
run_one_freq() {
local band="$1"
local serial="${SERIAL[$band]}"
local freq="${FREQ_HZ[$band]}"
if [[ -z "$serial" ]]; then
log "Для band=$band пустой serial, пропускаю"
return 0
fi
local ts out_dir log_file
ts="$(date '+%F_%H-%M-%S')"
out_dir="$BASE_DIR/$band/$ts"
log_file="$out_dir/run.log"
log "Старт band=$band serial=$serial freq=$freq dir=$out_dir"
if ! mkdir -p "$out_dir"; then
log "Не удалось создать каталог $out_dir"
return 1
fi
stop_band_service "$band"
"$PYTHON_BIN" "$SCRIPT_PATH" \
--serial "$serial" \
--freq "$freq" \
--save-dir "$out_dir" \
--file-tag "${band}_" \
--samp-rate "$SAMP_RATE" \
--split-size "$SPLIT_SIZE" \
--delay "$DELAY" \
--rf-gain "$RF_GAIN" \
--if-gain "$IF_GAIN" \
--bb-gain "$BB_GAIN" \
>"$log_file" 2>&1 &
local pid=$!
CURRENT_CAPTURE_PID="$pid"
log "PID=$pid"
while kill -0 "$pid" 2>/dev/null; do
local cur_dir_size cur_total_size
cur_dir_size="$(dir_size_bytes "$out_dir")"
cur_total_size="$(total_size_bytes)"
if (( cur_total_size >= TOTAL_LIMIT_BYTES )); then
log "Достигнут общий лимит $lim_all GiB. Останавливаю PID=$pid"
kill -TERM "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
CURRENT_CAPTURE_PID=""
start_current_service
return 2
fi
if (( cur_dir_size >= PER_FREQ_LIMIT_BYTES )); then
log "Для band=$band достигнут лимит 3 GiB. Останавливаю PID=$pid"
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
break
fi
sleep 1
done
CURRENT_CAPTURE_PID=""
start_current_service
log "Завершен band=$band, размер=$(du -sh "$out_dir" | awk '{print $1}')"
return 0
}
main_loop() {
while true; do
local total_before cycle_start elapsed sleep_left
total_before="$(total_size_bytes)"
if (( total_before >= TOTAL_LIMIT_BYTES )); then
log "Общий размер уже >= GiB, выхожу"
break
fi
cycle_start="$(date +%s)"
log "Новый цикл"
for band in "${ORDER[@]}"; do
if (( $(total_size_bytes) >= TOTAL_LIMIT_BYTES )); then
log "Общий лимит достигнут внутри цикла, выхожу"
return 0
fi
run_one_freq "$band" || {
rc=$?
if [[ $rc -eq 2 ]]; then
log "Остановка по общему лимиту"
return 0
else
log "Ошибка записи band=$band rc=$rc"
return "$rc"
fi
}
done
if [[ "$RUN_ONCE" == "1" ]]; then
log "RUN_ONCE=1, завершаю работу после одного цикла"
break
fi
elapsed=$(( $(date +%s) - cycle_start ))
sleep_left=$(( CYCLE_SECONDS - elapsed ))
if (( sleep_left > 0 )); then
log "Цикл занял ${elapsed} сек, жду ${sleep_left} сек до следующего часа"
sleep "$sleep_left"
else
log "Цикл занял ${elapsed} сек, паузы нет"
fi
done
}
trap cleanup_capture EXIT
trap 'on_signal INT' INT
trap 'on_signal TERM' TERM
ensure_requirements
main_loop