Update 2026-04-22 12:00:29
This commit is contained in:
parent
9ac3e19ed1
commit
1c994fa175
@ -5,8 +5,8 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"network": {
|
"network": {
|
||||||
"default_interface": "enp3s0",
|
"default_interface": "eth0",
|
||||||
"default_host_ip": "192.168.123.222"
|
"default_host_ip": "auto"
|
||||||
},
|
},
|
||||||
|
|
||||||
"livox": {
|
"livox": {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@ -11,6 +12,29 @@ from typing import Any, Optional
|
|||||||
|
|
||||||
from SLAM_Validation import run_startup_self_check
|
from SLAM_Validation import run_startup_self_check
|
||||||
|
|
||||||
|
|
||||||
|
def _autodetect_g1_host_ip(default: str = "192.168.123.164") -> str:
|
||||||
|
"""
|
||||||
|
Return the local IPv4 on the G1's 192.168.123.0/24 network.
|
||||||
|
|
||||||
|
The Livox SDK binds a UDP socket to this address; using the wrong one
|
||||||
|
(e.g. the workstation IP on the Jetson, or vice versa) produces a
|
||||||
|
`bind failed` error storm. Auto-detecting lets the same SLAM_Config.json
|
||||||
|
work on both the Jetson (eth0 = 192.168.123.164) and the workstation
|
||||||
|
(enp3s0 = 192.168.123.222) without manual edits.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
out = subprocess.run(
|
||||||
|
["ip", "-4", "-o", "addr"], capture_output=True, text=True, timeout=2,
|
||||||
|
).stdout
|
||||||
|
for line in out.splitlines():
|
||||||
|
for tok in line.split():
|
||||||
|
if tok.startswith("192.168.123.") and "/" in tok:
|
||||||
|
return tok.split("/")[0]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return default
|
||||||
|
|
||||||
# ------------------------- config loader (jsonl) -------------------------
|
# ------------------------- config loader (jsonl) -------------------------
|
||||||
def load_slam_config() -> dict:
|
def load_slam_config() -> dict:
|
||||||
"""
|
"""
|
||||||
@ -130,9 +154,25 @@ def _safe_put(q: mp.Queue, item: Any, keep_latest: bool = False) -> None:
|
|||||||
def build_configs_from_json(cfg: dict) -> tuple[EngineConfig, FilterConfig, MapConfig, LocalizationConfig, RuntimeConfig]:
|
def build_configs_from_json(cfg: dict) -> tuple[EngineConfig, FilterConfig, MapConfig, LocalizationConfig, RuntimeConfig]:
|
||||||
maps_dir = _resolve_from_config_dir(str(cfg["app"]["maps_dir"]))
|
maps_dir = _resolve_from_config_dir(str(cfg["app"]["maps_dir"]))
|
||||||
|
|
||||||
|
# Resolve the Livox host IP. Accept the literal "auto" (or an env var) so
|
||||||
|
# the same config works on the Jetson and the workstation. Also trigger
|
||||||
|
# auto-detect if the config still holds the old workstation default but
|
||||||
|
# we're clearly not running on the workstation.
|
||||||
|
host_ip_cfg = str(cfg["network"]["default_host_ip"]).strip()
|
||||||
|
env_ip = os.environ.get("LIVOX_HOST_IP", "").strip()
|
||||||
|
if env_ip:
|
||||||
|
resolved_host_ip = env_ip
|
||||||
|
elif host_ip_cfg.lower() == "auto" or not host_ip_cfg:
|
||||||
|
resolved_host_ip = _autodetect_g1_host_ip()
|
||||||
|
else:
|
||||||
|
# If config says 192.168.123.222 but this machine doesn't own that IP,
|
||||||
|
# fall back to autodetect instead of crashing the Livox SDK.
|
||||||
|
detected = _autodetect_g1_host_ip(default=host_ip_cfg)
|
||||||
|
resolved_host_ip = detected if detected != host_ip_cfg else host_ip_cfg
|
||||||
|
|
||||||
eng = EngineConfig(
|
eng = EngineConfig(
|
||||||
config_file=_resolve_from_config_dir(str(cfg["livox"]["config_file"])),
|
config_file=_resolve_from_config_dir(str(cfg["livox"]["config_file"])),
|
||||||
host_ip=cfg["network"]["default_host_ip"],
|
host_ip=resolved_host_ip,
|
||||||
max_range=float(cfg["slam"]["max_range"]),
|
max_range=float(cfg["slam"]["max_range"]),
|
||||||
slam_voxel_size=float(cfg["slam"]["slam_voxel_size"]),
|
slam_voxel_size=float(cfg["slam"]["slam_voxel_size"]),
|
||||||
pre_downsample_stride=int(cfg["slam"]["pre_downsample_stride"]),
|
pre_downsample_stride=int(cfg["slam"]["pre_downsample_stride"]),
|
||||||
|
|||||||
@ -65,12 +65,14 @@ def slam_worker(
|
|||||||
run_cfg: RuntimeConfig,
|
run_cfg: RuntimeConfig,
|
||||||
):
|
):
|
||||||
# ─────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
# Redirect this subprocess's stderr to logs/lidar_sdk.log. The Livox
|
# Redirect this subprocess's stdout AND stderr to logs/lidar_sdk.log.
|
||||||
# C++ SDK prints directly to stderr (not Python logging) and spams
|
# The Livox C++ SDK writes its `[console] [error] ...` stream to both
|
||||||
# thousands of lines/second when the LiDAR is offline or flaky. Keeping
|
# streams, and its `bind failed` / `Create socket ...` messages go to
|
||||||
# it in a file lets us still debug Livox issues without the errors
|
# stdout. Without this dup2, a disconnected or misconfigured LiDAR
|
||||||
# drowning the interactive terminal. stdout stays attached so our own
|
# floods Marcus's terminal with thousands of lines/second. The worker
|
||||||
# Python `print`s (pose updates, status lines) still reach the terminal.
|
# communicates status upward through the multiprocessing queues, so
|
||||||
|
# losing stdout/stderr here is fine — nothing the operator needs to
|
||||||
|
# see is lost.
|
||||||
# ─────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
import os as _os, sys as _sys
|
import os as _os, sys as _sys
|
||||||
try:
|
try:
|
||||||
@ -81,7 +83,9 @@ def slam_worker(
|
|||||||
_os.makedirs(_log_dir, exist_ok=True)
|
_os.makedirs(_log_dir, exist_ok=True)
|
||||||
_err_path = _os.path.join(_log_dir, "lidar_sdk.log")
|
_err_path = _os.path.join(_log_dir, "lidar_sdk.log")
|
||||||
_fd = _os.open(_err_path, _os.O_WRONLY | _os.O_CREAT | _os.O_APPEND, 0o644)
|
_fd = _os.open(_err_path, _os.O_WRONLY | _os.O_CREAT | _os.O_APPEND, 0o644)
|
||||||
_os.dup2(_fd, 2) # replace stderr FD so even C++ libs are captured
|
_os.dup2(_fd, 1) # replace stdout FD so C++ printf/puts are captured
|
||||||
|
_os.dup2(_fd, 2) # replace stderr FD for C++ std::cerr / spdlog
|
||||||
|
_sys.stdout = _os.fdopen(1, "w", buffering=1)
|
||||||
_sys.stderr = _os.fdopen(2, "w", buffering=1)
|
_sys.stderr = _os.fdopen(2, "w", buffering=1)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # never crash just because the log redirect failed
|
pass # never crash just because the log redirect failed
|
||||||
@ -3343,6 +3347,22 @@ def slam_worker(
|
|||||||
st("ERROR", f"Config missing: {cfg_path}")
|
st("ERROR", f"Config missing: {cfg_path}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Rewrite the per-host IPs inside mid360_config.json before the SDK
|
||||||
|
# reads them. The shipped file has the workstation IP hardcoded
|
||||||
|
# four times (cmd_data_ip / push_msg_ip / point_data_ip / imu_data_ip),
|
||||||
|
# which makes Livox's SDK bind() fail on any machine that isn't the
|
||||||
|
# workstation. host_ip here is the value resolved by SLAM_engine
|
||||||
|
# (either config, env, or auto-detected off 192.168.123.0/24).
|
||||||
|
try:
|
||||||
|
_mcfg = json.loads(cfg_path.read_text())
|
||||||
|
_hni = _mcfg.get("MID360", {}).get("host_net_info", {})
|
||||||
|
for _k in ("cmd_data_ip", "push_msg_ip", "point_data_ip", "imu_data_ip"):
|
||||||
|
if _k in _hni:
|
||||||
|
_hni[_k] = str(eng_cfg.host_ip)
|
||||||
|
cfg_path.write_text(json.dumps(_mcfg, indent=2))
|
||||||
|
except Exception as _e:
|
||||||
|
st("WARN", f"could not update mid360_config.json host IPs: {_e}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
lidar = Livox2(
|
lidar = Livox2(
|
||||||
str(cfg_path),
|
str(cfg_path),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user