完善夹爪界面、跨平台打包与运行日志
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import atexit
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
_LOG_STREAM = None
|
||||
|
||||
|
||||
def _cleanup_old_sessions(log_root, keep_sessions):
|
||||
sessions = sorted(
|
||||
(path for path in log_root.iterdir() if path.is_dir()),
|
||||
key=lambda path: path.stat().st_mtime,
|
||||
reverse=True,
|
||||
)
|
||||
for old_session in sessions[max(1, int(keep_sessions)) :]:
|
||||
try:
|
||||
shutil.rmtree(old_session)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def _redirect_standard_streams(stream):
|
||||
for descriptor in (1, 2):
|
||||
try:
|
||||
os.dup2(stream.fileno(), descriptor)
|
||||
except OSError:
|
||||
pass
|
||||
sys.stdout = stream
|
||||
sys.stderr = stream
|
||||
|
||||
|
||||
def _install_exception_hooks(stream):
|
||||
def handle_exception(exc_type, exc_value, exc_traceback):
|
||||
print("\n[unhandled exception]", file=stream)
|
||||
traceback.print_exception(exc_type, exc_value, exc_traceback, file=stream)
|
||||
stream.flush()
|
||||
|
||||
sys.excepthook = handle_exception
|
||||
|
||||
try:
|
||||
import threading
|
||||
|
||||
def handle_thread_exception(args):
|
||||
print(f"\n[thread exception] name={args.thread.name}", file=stream)
|
||||
traceback.print_exception(
|
||||
args.exc_type,
|
||||
args.exc_value,
|
||||
args.exc_traceback,
|
||||
file=stream,
|
||||
)
|
||||
stream.flush()
|
||||
|
||||
threading.excepthook = handle_thread_exception
|
||||
except (AttributeError, ImportError):
|
||||
pass
|
||||
|
||||
|
||||
def configure_frozen_logging(app_name="GripperDemo02", keep_sessions=10):
|
||||
global _LOG_STREAM
|
||||
if _LOG_STREAM is not None:
|
||||
return Path(_LOG_STREAM.name)
|
||||
if not getattr(sys, "frozen", False):
|
||||
return None
|
||||
|
||||
app_dir = Path(sys.executable).resolve().parent
|
||||
log_root = app_dir / "logs"
|
||||
log_root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
session_env = f"{app_name.upper()}_LOG_SESSION"
|
||||
session_name = os.environ.get(session_env)
|
||||
is_main_process = not session_name
|
||||
if is_main_process:
|
||||
session_name = datetime.now().strftime("%Y%m%d_%H%M%S") + f"_{os.getpid()}"
|
||||
os.environ[session_env] = session_name
|
||||
_cleanup_old_sessions(log_root, keep_sessions)
|
||||
|
||||
session_dir = log_root / session_name
|
||||
session_dir.mkdir(parents=True, exist_ok=True)
|
||||
log_name = "main.log" if is_main_process else f"worker_{os.getpid()}.log"
|
||||
log_path = session_dir / log_name
|
||||
_LOG_STREAM = open(log_path, "a", encoding="utf-8", buffering=1)
|
||||
_redirect_standard_streams(_LOG_STREAM)
|
||||
_install_exception_hooks(_LOG_STREAM)
|
||||
|
||||
if is_main_process:
|
||||
try:
|
||||
(log_root / "latest.txt").write_text(session_name + "\n", encoding="utf-8")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
print("=" * 72)
|
||||
print(f"{app_name} runtime log")
|
||||
print(f"time={datetime.now().isoformat(timespec='seconds')}")
|
||||
print(f"pid={os.getpid()} role={'main' if is_main_process else 'worker'}")
|
||||
print(f"executable={sys.executable}")
|
||||
print(f"log={log_path}")
|
||||
print(f"argv={sys.argv!r}")
|
||||
print("=" * 72)
|
||||
|
||||
atexit.register(_LOG_STREAM.flush)
|
||||
return log_path
|
||||
@@ -1,5 +1,6 @@
|
||||
import time
|
||||
import threading
|
||||
from collections import deque
|
||||
|
||||
from ..common.filters import clamp
|
||||
|
||||
@@ -22,7 +23,7 @@ class SideTriggerGripController:
|
||||
self.status_callback = status_callback
|
||||
self._stop_event = threading.Event()
|
||||
self._manual_lock = threading.Lock()
|
||||
self._manual_command = None
|
||||
self._manual_commands = deque()
|
||||
self._mode_lock = threading.Lock()
|
||||
self._control_mode = "smart"
|
||||
|
||||
@@ -47,7 +48,13 @@ class SideTriggerGripController:
|
||||
"position": position,
|
||||
}
|
||||
with self._manual_lock:
|
||||
self._manual_command = payload
|
||||
if command == "move_position":
|
||||
self._manual_commands = deque(
|
||||
item
|
||||
for item in self._manual_commands
|
||||
if item.get("command") != "move_position"
|
||||
)
|
||||
self._manual_commands.append(payload)
|
||||
|
||||
def set_control_mode(self, mode):
|
||||
if mode not in {"smart", "manual"}:
|
||||
@@ -61,9 +68,9 @@ class SideTriggerGripController:
|
||||
|
||||
def _pop_manual_command(self):
|
||||
with self._manual_lock:
|
||||
command = self._manual_command
|
||||
self._manual_command = None
|
||||
return command
|
||||
if not self._manual_commands:
|
||||
return None
|
||||
return self._manual_commands.popleft()
|
||||
|
||||
def run(self):
|
||||
self._stop_event.clear()
|
||||
@@ -828,9 +835,20 @@ class SideTriggerGripController:
|
||||
action,
|
||||
trigger_armed,
|
||||
):
|
||||
left_fps = 0.0
|
||||
right_fps = 0.0
|
||||
try:
|
||||
if len(self.reader.latest) >= 2:
|
||||
left_sample = self.reader.latest[0] or {}
|
||||
right_sample = self.reader.latest[1] or {}
|
||||
left_fps = float(left_sample.get("fps", 0.0) or 0.0)
|
||||
right_fps = float(right_sample.get("fps", 0.0) or 0.0)
|
||||
except (AttributeError, TypeError, ValueError):
|
||||
pass
|
||||
print(
|
||||
f"L={feedback.left_normal:7.3f}{self.normal_unit} "
|
||||
f"R={feedback.right_normal:7.3f}{self.normal_unit} "
|
||||
f"fps=({left_fps:5.1f},{right_fps:5.1f}) "
|
||||
f"maxN={feedback.max_normal:7.3f}{self.normal_unit} "
|
||||
f"shear={feedback.max_shear:7.3f}{self.shear_unit} "
|
||||
f"trigger={int(trigger)} armed={int(trigger_armed)} "
|
||||
|
||||
@@ -6,6 +6,7 @@ import inspect
|
||||
import multiprocessing as mp
|
||||
import queue
|
||||
import time
|
||||
import traceback
|
||||
from multiprocessing import Process, Queue
|
||||
|
||||
import cv2
|
||||
@@ -248,6 +249,8 @@ def _sensor_force_worker(
|
||||
time.sleep(sleep_time)
|
||||
|
||||
except Exception as exc:
|
||||
print(f"sensor {sensor_id} worker failed: {exc}")
|
||||
traceback.print_exc()
|
||||
_put_latest(
|
||||
result_queue,
|
||||
{
|
||||
|
||||
+618
-105
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user