Files
xMonitor/scripts/start_monitor_sender.sh
2026-04-15 18:25:03 +08:00

220 lines
6.3 KiB
Bash

#!/usr/bin/env bash
set -Eeuo pipefail
timestamp() {
date '+%Y-%m-%d %H:%M:%S'
}
log() {
printf '[%s] %s\n' "$(timestamp)" "$*"
}
fail() {
log "ERROR: $*"
exit 1
}
report_err() {
local exit_code=$?
local line_no=${1:-unknown}
local cmd=${2:-unknown}
log "ERROR: command failed at line ${line_no}: ${cmd} (exit=${exit_code})"
exit "$exit_code"
}
trap 'report_err "${LINENO}" "${BASH_COMMAND}"' ERR
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEFAULT_APP_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
ENV_FILE="${ENV_FILE:-/etc/xmonitor/xmonitor.env}"
if [[ -f "${ENV_FILE}" ]]; then
# shellcheck disable=SC1090
source "${ENV_FILE}"
fi
RUN_USER="${RUN_USER:-$(id -un)}"
APP_DIR="${APP_DIR:-${DEFAULT_APP_DIR}}"
LOG_DIR="${LOG_DIR:-${APP_DIR}/logs}"
HOTSPOT_SSID="${HOTSPOT_SSID:-}"
HOTSPOT_PASSWORD="${HOTSPOT_PASSWORD:-}"
SERVER_IP="${SERVER_IP:-10.0.0.5}"
SERVER_PORT="${SERVER_PORT:-9000}"
ROS_SETUP="${ROS_SETUP:-/home/${RUN_USER}/ros2ws/install/setup.bash}"
PYTHON_BIN="${PYTHON_BIN:-python3}"
WIFI_RETRY_INTERVAL="${WIFI_RETRY_INTERVAL:-5}"
PING_RETRY_INTERVAL="${PING_RETRY_INTERVAL:-5}"
VENV_ACTIVATE="${APP_DIR}/.venv/bin/activate"
LOG_RETENTION_COUNT="${LOG_RETENTION_COUNT:-20}"
mkdir -p "${LOG_DIR}"
LOG_FILE="${LOG_DIR}/monitor_sender_$(date '+%Y%m%d_%H%M%S').log"
touch "${LOG_FILE}"
exec > >(tee -a "${LOG_FILE}") 2>&1
require_command() {
local command_name=$1
command -v "${command_name}" >/dev/null 2>&1 || fail "missing required command: ${command_name}"
}
prune_old_logs() {
local prune_from
local old_logs=()
[[ "${LOG_RETENTION_COUNT}" =~ ^[0-9]+$ ]] || fail "LOG_RETENTION_COUNT must be a positive integer"
(( LOG_RETENTION_COUNT >= 1 )) || fail "LOG_RETENTION_COUNT must be at least 1"
prune_from=$((LOG_RETENTION_COUNT + 1))
mapfile -t old_logs < <(ls -1dt "${LOG_DIR}"/monitor_sender_*.log 2>/dev/null | tail -n +"${prune_from}")
if ((${#old_logs[@]} > 0)); then
rm -f -- "${old_logs[@]}"
log "Pruned ${#old_logs[@]} old log file(s); keeping latest ${LOG_RETENTION_COUNT}"
fi
}
source_with_relaxed_mode() {
local target_file=$1
local shell_flags=$-
local err_trap
local source_status=0
err_trap="$(trap -p ERR || true)"
trap - ERR
set +eu
# shellcheck disable=SC1090,SC1091
source "${target_file}"
source_status=$?
[[ "${shell_flags}" == *e* ]] && set -e || set +e
[[ "${shell_flags}" == *u* ]] && set -u || set +u
if [[ -n "${err_trap}" ]]; then
eval "${err_trap}"
fi
return "${source_status}"
}
get_wifi_device() {
nmcli -t -f DEVICE,TYPE device status | awk -F: '$2 == "wifi" { print $1; exit }'
}
get_active_connection_name() {
local wifi_device=$1
nmcli -g GENERAL.CONNECTION device show "${wifi_device}" 2>/dev/null | head -n1 | tr -d '\r'
}
get_active_ssid() {
local wifi_device=$1
local active_connection
active_connection="$(get_active_connection_name "${wifi_device}" || true)"
if [[ -z "${active_connection}" || "${active_connection}" == "--" ]]; then
return 1
fi
nmcli -g 802-11-wireless.ssid connection show "${active_connection}" 2>/dev/null | head -n1 | tr -d '\r'
}
is_hotspot_connected() {
local wifi_device=$1
local active_ssid
active_ssid="$(get_active_ssid "${wifi_device}" || true)"
[[ -n "${active_ssid}" && "${active_ssid}" == "${HOTSPOT_SSID}" ]]
}
connect_hotspot() {
local wifi_device=$1
while true; do
if is_hotspot_connected "${wifi_device}"; then
log "Wi-Fi already connected to hotspot '${HOTSPOT_SSID}'"
return 0
fi
log "Trying hotspot '${HOTSPOT_SSID}' on Wi-Fi device '${wifi_device}'"
nmcli device wifi rescan ifname "${wifi_device}" >/dev/null 2>&1 || true
if nmcli connection up "${HOTSPOT_SSID}" ifname "${wifi_device}" >/dev/null 2>&1; then
sleep 2
if is_hotspot_connected "${wifi_device}"; then
log "Connected to hotspot '${HOTSPOT_SSID}' via saved NetworkManager profile"
return 0
fi
fi
if [[ -n "${HOTSPOT_PASSWORD}" ]]; then
if nmcli device wifi connect "${HOTSPOT_SSID}" password "${HOTSPOT_PASSWORD}" ifname "${wifi_device}" >/dev/null 2>&1; then
sleep 2
if is_hotspot_connected "${wifi_device}"; then
log "Connected to hotspot '${HOTSPOT_SSID}' via direct nmcli connect"
return 0
fi
fi
else
log "HOTSPOT_PASSWORD is empty; skipping direct nmcli connect and relying on saved profile"
fi
log "Hotspot '${HOTSPOT_SSID}' is not ready yet; retrying in ${WIFI_RETRY_INTERVAL}s"
sleep "${WIFI_RETRY_INTERVAL}"
done
}
wait_for_server() {
while true; do
if ping -c 1 -W 2 "${SERVER_IP}" >/dev/null 2>&1; then
log "Server '${SERVER_IP}' is reachable"
return 0
fi
log "Server '${SERVER_IP}' is not reachable yet; retrying in ${PING_RETRY_INTERVAL}s"
sleep "${PING_RETRY_INTERVAL}"
done
}
log "Starting xMonitor sender bootstrap"
log "Using APP_DIR='${APP_DIR}'"
log "Log file: ${LOG_FILE}"
if [[ -f "${ENV_FILE}" ]]; then
log "Loaded environment from '${ENV_FILE}'"
else
log "Environment file '${ENV_FILE}' not found; using current shell environment/defaults"
fi
prune_old_logs
[[ -n "${HOTSPOT_SSID}" ]] || fail "HOTSPOT_SSID must be set in '${ENV_FILE}' or exported before launching the script"
[[ -d "${APP_DIR}" ]] || fail "APP_DIR does not exist: ${APP_DIR}"
[[ -f "${APP_DIR}/monitor_sender.py" ]] || fail "monitor_sender.py not found in ${APP_DIR}"
[[ -f "${ROS_SETUP}" ]] || fail "ROS setup file not found: ${ROS_SETUP}"
[[ -f "${VENV_ACTIVATE}" ]] || fail "Python virtualenv activate script not found: ${VENV_ACTIVATE}"
require_command nmcli
require_command ping
require_command tee
require_command "${PYTHON_BIN}"
if [[ "$(id -un)" != "${RUN_USER}" ]]; then
log "Warning: script is running as '$(id -un)' but RUN_USER is '${RUN_USER}'"
fi
WIFI_DEVICE="$(get_wifi_device)"
[[ -n "${WIFI_DEVICE}" ]] || fail "No Wi-Fi device was found by NetworkManager"
log "Detected Wi-Fi device '${WIFI_DEVICE}'"
connect_hotspot "${WIFI_DEVICE}"
wait_for_server
log "Sourcing ROS environment '${ROS_SETUP}'"
source_with_relaxed_mode "${ROS_SETUP}"
log "Activating Python virtualenv '${VENV_ACTIVATE}'"
source_with_relaxed_mode "${VENV_ACTIVATE}"
cd "${APP_DIR}"
log "Launching monitor_sender.py --ip ${SERVER_IP} --port ${SERVER_PORT}"
exec "${PYTHON_BIN}" monitor_sender.py --ip "${SERVER_IP}" --port "${SERVER_PORT}"