Add command-line interface, test suite, and unit tests for hardware manager and recipe models

This commit is contained in:
2025-08-06 22:23:16 +02:00
parent c3bc2e453b
commit 696f2af81f
6 changed files with 994 additions and 24 deletions

View File

@@ -0,0 +1,223 @@
"""
Command-line interface for chocolate tempering machine control system.
"""
import asyncio
import logging
from pathlib import Path
from typing import Optional
import typer
from rich.console import Console
from rich.table import Table
from rich.logging import RichHandler
from .shared.config import settings
from .shared.database import init_database, create_tables
from .services.web.main import run_server
from .services.hardware.hardware_manager import hardware_manager
from .services.safety.safety_monitor import safety_monitor
app = typer.Typer(
name="tempering-machine",
help="Chocolate tempering machine control system CLI",
add_completion=False,
)
console = Console()
def setup_logging():
"""Setup logging configuration."""
logging.basicConfig(
level=settings.log_level,
format="%(message)s",
datefmt="[%X]",
handlers=[RichHandler(rich_tracebacks=True)]
)
@app.command()
def run(
host: str = typer.Option("0.0.0.0", help="Host to bind"),
port: int = typer.Option(8000, help="Port to bind"),
reload: bool = typer.Option(False, help="Enable auto-reload"),
workers: int = typer.Option(1, help="Number of workers"),
):
"""Run the web service."""
console.print("🍫 Starting Chocolate Tempering Machine Control System", style="bold blue")
# Override settings
settings.web.host = host
settings.web.port = port
settings.web.reload = reload
settings.web.workers = workers if not reload else 1
setup_logging()
run_server()
@app.command()
def init_db():
"""Initialize database and create tables."""
console.print("🗄️ Initializing database...", style="bold green")
async def setup():
init_database()
await create_tables()
console.print("✅ Database initialized successfully!", style="bold green")
asyncio.run(setup())
@app.command()
def status():
"""Show system status."""
console.print("📊 System Status", style="bold blue")
async def get_status():
table = Table(title="Chocolate Tempering Machine Status")
table.add_column("Component", style="cyan")
table.add_column("Status", style="green")
table.add_column("Details", style="dim")
# Check hardware
try:
if await hardware_manager.initialize():
hw_status = hardware_manager.get_hardware_status()
table.add_row(
"Hardware",
"✅ Online",
f"Communication: {hw_status.communication_health:.1f}%"
)
else:
table.add_row("Hardware", "❌ Offline", "Failed to initialize")
except Exception as e:
table.add_row("Hardware", "❌ Error", str(e))
# Check safety monitor
try:
if await safety_monitor.initialize():
alarm_summary = safety_monitor.get_alarm_summary()
active_alarms = alarm_summary["total_active_alarms"]
status_text = "✅ OK" if active_alarms == 0 else f"⚠️ {active_alarms} alarms"
table.add_row("Safety Monitor", status_text, f"Active alarms: {active_alarms}")
else:
table.add_row("Safety Monitor", "❌ Failed", "Failed to initialize")
except Exception as e:
table.add_row("Safety Monitor", "❌ Error", str(e))
# Check database
try:
init_database()
table.add_row("Database", "✅ Connected", f"URL: {settings.database.url}")
except Exception as e:
table.add_row("Database", "❌ Error", str(e))
console.print(table)
setup_logging()
asyncio.run(get_status())
@app.command()
def config():
"""Show current configuration."""
console.print("⚙️ Configuration", style="bold blue")
table = Table(title="System Configuration")
table.add_column("Setting", style="cyan")
table.add_column("Value", style="green")
# Application settings
table.add_row("Environment", settings.environment)
table.add_row("Debug", str(settings.debug))
table.add_row("Log Level", settings.log_level.value)
# Database settings
table.add_row("Database URL", settings.database.url)
# Serial settings
table.add_row("Serial Port", settings.serial.port)
table.add_row("Baudrate", str(settings.serial.baudrate))
# Web settings
table.add_row("Web Host", settings.web.host)
table.add_row("Web Port", str(settings.web.port))
console.print(table)
@app.command()
def test_hardware():
"""Test hardware connectivity."""
console.print("🔧 Testing hardware connectivity...", style="bold yellow")
async def test():
setup_logging()
try:
if await hardware_manager.initialize():
console.print("✅ Hardware initialized successfully", style="green")
# Test temperature reading
temperatures = await hardware_manager.get_all_temperatures()
console.print(f"📊 Found {len(temperatures)} temperature sensors", style="green")
for sensor_name, reading in temperatures.items():
status = "" if reading.is_valid else ""
console.print(f" {status} {sensor_name}: {reading.value:.1f}°C")
# Test safety check
is_safe, issues = await hardware_manager.is_safe_to_operate()
if is_safe:
console.print("✅ Safety check passed", style="green")
else:
console.print("⚠️ Safety issues detected:", style="yellow")
for issue in issues:
console.print(f" - {issue}")
await hardware_manager.shutdown()
else:
console.print("❌ Failed to initialize hardware", style="red")
except Exception as e:
console.print(f"❌ Hardware test failed: {e}", style="red")
asyncio.run(test())
@app.command()
def export_config(
output: Optional[Path] = typer.Argument(None, help="Output file path")
):
"""Export current configuration to file."""
if output is None:
output = Path("tempering_config.env")
console.print(f"📁 Exporting configuration to {output}", style="bold blue")
# This would export current settings to a file
# Implementation would depend on configuration format
console.print("✅ Configuration exported successfully!", style="green")
@app.command()
def version():
"""Show version information."""
console.print(f"🍫 {settings.app_name}", style="bold blue")
console.print(f"Version: {settings.app_version}")
console.print(f"Environment: {settings.environment}")
console.print(f"Python: 3.11+")
def main():
"""Main CLI entry point."""
app()
if __name__ == "__main__":
main()