RSI-PI/src/RSIPI/logging_api.py
Adam 50e6df9719 Implement Phase 1 & Phase 5: Code quality improvements and namespaced API architecture
Major refactoring to improve code quality, maintainability, and API organization
for publication-quality research software.

Phase 1 - Code Quality Foundation:
- Add comprehensive type hints across all core modules (500+ annotations)
- Create custom exception hierarchy with 20+ specialized exceptions
- Replace all print() statements with proper logging (debug, info, warning, error, critical)
- Enhance all docstrings with Args/Returns/Raises sections
- Improve error handling with exception chaining

Modified core modules:
- rsi_client.py: State machine with typed exceptions, full type hints
- network_handler.py: CSV logging and UDP communication with typed interfaces
- config_parser.py: XML parsing with proper exception handling
- safety_manager.py: Safety validation with typed limits
- __init__.py: Clean exports for all public APIs

Phase 5 - Namespaced API Architecture:
- Restructure RSIAPI as orchestrator providing 9 specialized namespaces
- Create clean separation of concerns with dedicated API classes

New namespace APIs:
- motion_api.py: Motion control (Cartesian, joints, trajectories)
- io_api.py: Digital I/O control
- krl_api.py: KRL program manipulation utilities
- safety_api.py: Safety management and limits
- monitoring_api.py: Live data access and monitoring
- logging_api.py: CSV data logging
- diagnostics_api.py: Network diagnostics (Phase 2 placeholder)
- viz_api.py: Static and live visualization
- tools_api.py: Utilities, debugging, inspection

Breaking Changes:
- No backward compatibility - clean slate API design
- Old: api.start_rsi() → New: api.start()
- Old: api.update_cartesian(...) → New: api.motion.update_cartesian(...)
- See migration guide in PHASE_5_SUMMARY.md

Benefits:
- Organized and discoverable API structure
- Scalable architecture for future enhancements
- Type-safe with full IDE autocomplete support
- Easier testing and maintenance
- Professional industry-standard design pattern

Files changed: 6 modified, 9 new (net -37 lines, improved organization)
2026-01-16 23:49:45 +00:00

149 lines
4.5 KiB
Python

"""CSV logging API namespace for RSIPI."""
import logging
import datetime
import os
from typing import Optional, TYPE_CHECKING
import pandas as pd
if TYPE_CHECKING:
from .rsi_client import RSIClient
class LoggingAPI:
"""
CSV data logging interface for KUKA RSI robot control.
Manages high-frequency data logging to CSV files with British date format
timestamps. Logging runs in a separate process to avoid timing interference
with the real-time control loop.
"""
def __init__(self, client: 'RSIClient') -> None:
"""
Initialize LoggingAPI namespace.
Args:
client: RSIClient instance for logging control
"""
self.client = client
def start(self, filename: Optional[str] = None) -> str:
"""
Start CSV logging to file.
Creates a background logging process that writes send/receive variables
to CSV with British date format timestamps (DD/MM/YYYY HH:MM:SS.mmm).
Args:
filename: Optional output file path. Auto-generated if not provided
with format: logs/DD-MM-YYYY_HH-MM-SS.csv
Returns:
Path to the log file being written
Example:
>>> # Auto-generated filename
>>> path = api.logging.start()
>>> print(path)
logs/16-01-2026_14-32-45.csv
>>> # Custom filename
>>> path = api.logging.start('my_experiment.csv')
>>> print(path)
my_experiment.csv
Note:
Logging runs in a separate process and uses a queue-based buffering
system to prevent blocking the real-time control loop. If the queue
fills, old entries are dropped rather than blocking.
"""
if not filename:
timestamp = datetime.datetime.now().strftime("%d-%m-%Y_%H-%M-%S")
filename = f"logs/{timestamp}.csv"
# Ensure logs directory exists
log_dir = os.path.dirname(filename)
if log_dir and not os.path.exists(log_dir):
os.makedirs(log_dir, exist_ok=True)
logging.info(f"Created logging directory: {log_dir}")
self.client.start_logging(filename)
logging.info(f"CSV logging started: {filename}")
return filename
def stop(self) -> str:
"""
Stop CSV logging.
Signals the logging process to flush remaining data and close the file.
The logging process will terminate gracefully.
Returns:
Status message
Example:
>>> api.logging.stop()
'CSV logging stopped'
Note:
There may be a brief delay (up to 2 seconds) while the logging
process completes writing buffered data and shuts down.
"""
self.client.stop_logging()
logging.info("CSV logging stopped")
return "CSV logging stopped"
def is_active(self) -> bool:
"""
Check if CSV logging is currently running.
Returns:
True if logging process is active and writing data
Example:
>>> api.logging.start('test.csv')
>>> api.logging.is_active()
True
>>> api.logging.stop()
>>> api.logging.is_active()
False
"""
return self.client.is_logging_active()
def export(self, filename: str = "movement_log.csv") -> str:
"""
Export recorded movement data to CSV (if logger is attached).
This is separate from the real-time CSV logging and is intended for
exporting pre-recorded data from an attached logger object.
Args:
filename: Output CSV file path
Returns:
Status message with export path
Raises:
RuntimeError: If no logger is attached or no data available
Example:
>>> api.logging.export('my_data.csv')
'Movement data exported to my_data.csv'
Note:
This method is currently reserved for future use with an attached
data logger. Real-time logging uses start()/stop() instead.
"""
if not hasattr(self.client, "logger") or self.client.logger is None:
raise RuntimeError("No logger attached to RSI client")
data = self.client.get_movement_data()
if not data:
raise RuntimeError("No data available to export")
df = pd.DataFrame(data)
df.to_csv(filename, index=False)
logging.info(f"Movement data exported to {filename}")
return f"Movement data exported to {filename}"