| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 |
- """
- Arbitration Configuration Management for Trixy
- This module provides comprehensive configuration management for the arbitration system,
- including algorithm selection, timing parameters, satellite priorities, and custom
- plugin-based arbitration settings.
- The configuration system supports:
- - Multiple arbitration algorithms (volume-based, distance-based, priority-based, round-robin, custom)
- - Configurable timing parameters (collection window, timeouts)
- - Room and satellite priority settings
- - Custom algorithm plugin integration
- - Runtime configuration updates
- - Validation and error handling
- Usage:
- from trixy_core.arbitration import ArbitrationConfig, ArbitrationAlgorithm
-
- # Create configuration with defaults
- config = ArbitrationConfig()
-
- # Configure algorithm
- config.primary_algorithm = ArbitrationAlgorithm.VOLUME_BASED
- config.fallback_algorithm = ArbitrationAlgorithm.PRIORITY_BASED
-
- # Set room priorities
- config.set_room_priority("kitchen", 1.0)
- config.set_room_priority("living_room", 0.8)
-
- # Configure timing
- config.collection_window_seconds = 1.0
- config.arbitration_timeout_seconds = 2.0
- """
- from dataclasses import dataclass, field
- from enum import Enum
- from typing import Dict, List, Optional, Any, Union
- import json
- import os
- from pathlib import Path
- def pprint(message: str) -> None:
- """Arbitration config logging function."""
- print(f"[ARBITRATION_CONFIG] {message}")
- class ArbitrationAlgorithm(Enum):
- """Supported arbitration algorithms."""
- VOLUME_BASED = "volume_based"
- DISTANCE_BASED = "distance_based"
- PRIORITY_BASED = "priority_based"
- ROUND_ROBIN = "round_robin"
- CUSTOM = "custom"
- HYBRID = "hybrid" # Combination of multiple algorithms
- class ArbitrationMode(Enum):
- """Arbitration operation modes."""
- STRICT = "strict" # Strict algorithm adherence
- ADAPTIVE = "adaptive" # Adapt based on conditions
- LEARNING = "learning" # Learn from user preferences
- EMERGENCY = "emergency" # Emergency mode (fastest response)
- class SatellitePriority(Enum):
- """Satellite priority levels."""
- CRITICAL = 1.0
- HIGH = 0.8
- NORMAL = 0.6
- LOW = 0.4
- DISABLED = 0.0
- @dataclass
- class RoomSettings:
- """Configuration settings for a specific room."""
- priority: float = 0.6
- distance_weight: float = 1.0
- volume_threshold: float = 0.1
- preferred_satellite: Optional[str] = None
- disabled: bool = False
- custom_settings: Dict[str, Any] = field(default_factory=dict)
-
- def to_dict(self) -> Dict[str, Any]:
- """Convert to dictionary representation."""
- return {
- 'priority': self.priority,
- 'distance_weight': self.distance_weight,
- 'volume_threshold': self.volume_threshold,
- 'preferred_satellite': self.preferred_satellite,
- 'disabled': self.disabled,
- 'custom_settings': self.custom_settings
- }
-
- @classmethod
- def from_dict(cls, data: Dict[str, Any]) -> 'RoomSettings':
- """Create from dictionary representation."""
- return cls(
- priority=data.get('priority', 0.6),
- distance_weight=data.get('distance_weight', 1.0),
- volume_threshold=data.get('volume_threshold', 0.1),
- preferred_satellite=data.get('preferred_satellite'),
- disabled=data.get('disabled', False),
- custom_settings=data.get('custom_settings', {})
- )
- @dataclass
- class AlgorithmWeights:
- """Weights for hybrid arbitration algorithm."""
- volume_weight: float = 0.5
- distance_weight: float = 0.3
- priority_weight: float = 0.2
- confidence_weight: float = 0.1
- history_weight: float = 0.05
-
- def normalize(self) -> 'AlgorithmWeights':
- """Normalize weights to sum to 1.0."""
- total = (self.volume_weight + self.distance_weight +
- self.priority_weight + self.confidence_weight + self.history_weight)
-
- if total > 0:
- return AlgorithmWeights(
- volume_weight=self.volume_weight / total,
- distance_weight=self.distance_weight / total,
- priority_weight=self.priority_weight / total,
- confidence_weight=self.confidence_weight / total,
- history_weight=self.history_weight / total
- )
- return self
-
- def to_dict(self) -> Dict[str, float]:
- """Convert to dictionary representation."""
- return {
- 'volume_weight': self.volume_weight,
- 'distance_weight': self.distance_weight,
- 'priority_weight': self.priority_weight,
- 'confidence_weight': self.confidence_weight,
- 'history_weight': self.history_weight
- }
-
- @classmethod
- def from_dict(cls, data: Dict[str, float]) -> 'AlgorithmWeights':
- """Create from dictionary representation."""
- return cls(
- volume_weight=data.get('volume_weight', 0.5),
- distance_weight=data.get('distance_weight', 0.3),
- priority_weight=data.get('priority_weight', 0.2),
- confidence_weight=data.get('confidence_weight', 0.1),
- history_weight=data.get('history_weight', 0.05)
- )
- class ArbitrationConfigError(Exception):
- """Base exception for arbitration configuration errors."""
- pass
- class InvalidConfigurationError(ArbitrationConfigError):
- """Raised when configuration values are invalid."""
- pass
- class ConfigurationFileError(ArbitrationConfigError):
- """Raised when configuration file operations fail."""
- pass
- @dataclass
- class ArbitrationConfig:
- """
- Comprehensive configuration for the arbitration system.
-
- This class manages all configuration aspects of the arbitration system,
- including algorithm selection, timing parameters, satellite priorities,
- and custom settings.
- """
-
- # Algorithm configuration
- primary_algorithm: ArbitrationAlgorithm = ArbitrationAlgorithm.VOLUME_BASED
- fallback_algorithm: ArbitrationAlgorithm = ArbitrationAlgorithm.PRIORITY_BASED
- mode: ArbitrationMode = ArbitrationMode.ADAPTIVE
-
- # Timing configuration (in seconds)
- collection_window_seconds: float = 1.0
- arbitration_timeout_seconds: float = 2.0
- min_collection_time: float = 0.1
- max_wait_time: float = 5.0
-
- # Volume-based algorithm settings
- volume_threshold: float = 0.1
- volume_hysteresis: float = 0.05
- volume_smoothing_factor: float = 0.3
-
- # Distance-based algorithm settings
- distance_threshold: float = 1.0 # meters
- distance_falloff: float = 0.5
- use_estimated_distance: bool = True
-
- # Priority-based algorithm settings
- default_satellite_priority: float = 0.6
- room_priorities: Dict[str, RoomSettings] = field(default_factory=dict)
-
- # Round-robin algorithm settings
- round_robin_reset_interval: int = 100 # Reset after N arbitrations
- round_robin_fairness_mode: bool = True
-
- # Hybrid algorithm settings
- algorithm_weights: AlgorithmWeights = field(default_factory=AlgorithmWeights)
-
- # Advanced settings
- enable_learning: bool = True
- enable_analytics: bool = True
- enable_history_tracking: bool = True
- max_history_entries: int = 1000
-
- # Custom algorithm settings
- custom_algorithm_plugin: Optional[str] = None
- custom_algorithm_config: Dict[str, Any] = field(default_factory=dict)
-
- # Performance settings
- max_concurrent_arbitrations: int = 10
- thread_pool_size: int = 4
- enable_caching: bool = True
- cache_size: int = 100
-
- # Debugging and logging
- enable_debug_logging: bool = False
- log_all_decisions: bool = True
- log_performance_metrics: bool = True
-
- def __post_init__(self):
- """Post-initialization validation and setup."""
- self.validate()
- if not self.room_priorities:
- self._setup_default_room_priorities()
-
- def validate(self) -> None:
- """
- Validate configuration values.
-
- Raises:
- InvalidConfigurationError: If any configuration values are invalid
- """
- # Validate timing parameters
- if self.collection_window_seconds <= 0:
- raise InvalidConfigurationError("Collection window must be positive")
-
- if self.arbitration_timeout_seconds <= self.collection_window_seconds:
- raise InvalidConfigurationError(
- "Arbitration timeout must be greater than collection window"
- )
-
- if self.min_collection_time < 0 or self.min_collection_time > self.collection_window_seconds:
- raise InvalidConfigurationError(
- "Min collection time must be between 0 and collection window"
- )
-
- # Validate thresholds
- if not 0 <= self.volume_threshold <= 1:
- raise InvalidConfigurationError("Volume threshold must be between 0 and 1")
-
- if not 0 <= self.default_satellite_priority <= 1:
- raise InvalidConfigurationError("Default satellite priority must be between 0 and 1")
-
- # Validate performance settings
- if self.max_concurrent_arbitrations <= 0:
- raise InvalidConfigurationError("Max concurrent arbitrations must be positive")
-
- if self.thread_pool_size <= 0:
- raise InvalidConfigurationError("Thread pool size must be positive")
-
- # Validate hybrid algorithm weights
- if self.primary_algorithm == ArbitrationAlgorithm.HYBRID:
- self.algorithm_weights = self.algorithm_weights.normalize()
-
- pprint("Configuration validation completed successfully")
-
- def _setup_default_room_priorities(self) -> None:
- """Set up default room priorities."""
- default_rooms = {
- "kitchen": RoomSettings(priority=1.0),
- "living_room": RoomSettings(priority=0.8),
- "bedroom": RoomSettings(priority=0.6),
- "office": RoomSettings(priority=0.7),
- "bathroom": RoomSettings(priority=0.4),
- "garage": RoomSettings(priority=0.3),
- }
-
- for room_id, settings in default_rooms.items():
- self.room_priorities[room_id] = settings
-
- pprint(f"Set up default priorities for {len(default_rooms)} rooms")
-
- def set_room_priority(self, room_id: str, priority: float, **kwargs) -> None:
- """
- Set priority for a specific room.
-
- Args:
- room_id: Room identifier
- priority: Priority value (0.0 to 1.0)
- **kwargs: Additional room settings
- """
- if not 0 <= priority <= 1:
- raise InvalidConfigurationError(f"Priority must be between 0 and 1, got {priority}")
-
- if room_id not in self.room_priorities:
- self.room_priorities[room_id] = RoomSettings()
-
- self.room_priorities[room_id].priority = priority
-
- # Update additional settings
- for key, value in kwargs.items():
- if hasattr(self.room_priorities[room_id], key):
- setattr(self.room_priorities[room_id], key, value)
-
- pprint(f"Set priority for room '{room_id}' to {priority}")
-
- def get_room_priority(self, room_id: str) -> float:
- """
- Get priority for a specific room.
-
- Args:
- room_id: Room identifier
-
- Returns:
- float: Room priority (defaults to default_satellite_priority)
- """
- if room_id in self.room_priorities:
- return self.room_priorities[room_id].priority
- return self.default_satellite_priority
-
- def get_room_settings(self, room_id: str) -> RoomSettings:
- """
- Get complete settings for a specific room.
-
- Args:
- room_id: Room identifier
-
- Returns:
- RoomSettings: Room configuration settings
- """
- if room_id not in self.room_priorities:
- self.room_priorities[room_id] = RoomSettings(priority=self.default_satellite_priority)
-
- return self.room_priorities[room_id]
-
- def enable_room(self, room_id: str) -> None:
- """Enable arbitration for a specific room."""
- settings = self.get_room_settings(room_id)
- settings.disabled = False
- pprint(f"Enabled arbitration for room '{room_id}'")
-
- def disable_room(self, room_id: str) -> None:
- """Disable arbitration for a specific room."""
- settings = self.get_room_settings(room_id)
- settings.disabled = True
- pprint(f"Disabled arbitration for room '{room_id}'")
-
- def is_room_enabled(self, room_id: str) -> bool:
- """Check if arbitration is enabled for a specific room."""
- settings = self.get_room_settings(room_id)
- return not settings.disabled
-
- def set_algorithm_weights(self, **weights) -> None:
- """
- Set weights for hybrid algorithm.
-
- Args:
- **weights: Weight values (volume_weight, distance_weight, etc.)
- """
- for key, value in weights.items():
- if hasattr(self.algorithm_weights, key):
- setattr(self.algorithm_weights, key, value)
-
- self.algorithm_weights = self.algorithm_weights.normalize()
- pprint(f"Updated algorithm weights: {self.algorithm_weights.to_dict()}")
-
- def to_dict(self) -> Dict[str, Any]:
- """
- Convert configuration to dictionary representation.
-
- Returns:
- Dict[str, Any]: Configuration as dictionary
- """
- return {
- 'primary_algorithm': self.primary_algorithm.value,
- 'fallback_algorithm': self.fallback_algorithm.value,
- 'mode': self.mode.value,
- 'collection_window_seconds': self.collection_window_seconds,
- 'arbitration_timeout_seconds': self.arbitration_timeout_seconds,
- 'min_collection_time': self.min_collection_time,
- 'max_wait_time': self.max_wait_time,
- 'volume_threshold': self.volume_threshold,
- 'volume_hysteresis': self.volume_hysteresis,
- 'volume_smoothing_factor': self.volume_smoothing_factor,
- 'distance_threshold': self.distance_threshold,
- 'distance_falloff': self.distance_falloff,
- 'use_estimated_distance': self.use_estimated_distance,
- 'default_satellite_priority': self.default_satellite_priority,
- 'room_priorities': {
- room_id: settings.to_dict()
- for room_id, settings in self.room_priorities.items()
- },
- 'round_robin_reset_interval': self.round_robin_reset_interval,
- 'round_robin_fairness_mode': self.round_robin_fairness_mode,
- 'algorithm_weights': self.algorithm_weights.to_dict(),
- 'enable_learning': self.enable_learning,
- 'enable_analytics': self.enable_analytics,
- 'enable_history_tracking': self.enable_history_tracking,
- 'max_history_entries': self.max_history_entries,
- 'custom_algorithm_plugin': self.custom_algorithm_plugin,
- 'custom_algorithm_config': self.custom_algorithm_config,
- 'max_concurrent_arbitrations': self.max_concurrent_arbitrations,
- 'thread_pool_size': self.thread_pool_size,
- 'enable_caching': self.enable_caching,
- 'cache_size': self.cache_size,
- 'enable_debug_logging': self.enable_debug_logging,
- 'log_all_decisions': self.log_all_decisions,
- 'log_performance_metrics': self.log_performance_metrics,
- }
-
- @classmethod
- def from_dict(cls, data: Dict[str, Any]) -> 'ArbitrationConfig':
- """
- Create configuration from dictionary representation.
-
- Args:
- data: Configuration dictionary
-
- Returns:
- ArbitrationConfig: New configuration instance
- """
- config = cls()
-
- # Update basic fields
- for key, value in data.items():
- if key == 'primary_algorithm':
- config.primary_algorithm = ArbitrationAlgorithm(value)
- elif key == 'fallback_algorithm':
- config.fallback_algorithm = ArbitrationAlgorithm(value)
- elif key == 'mode':
- config.mode = ArbitrationMode(value)
- elif key == 'room_priorities':
- config.room_priorities = {
- room_id: RoomSettings.from_dict(settings_data)
- for room_id, settings_data in value.items()
- }
- elif key == 'algorithm_weights':
- config.algorithm_weights = AlgorithmWeights.from_dict(value)
- elif hasattr(config, key):
- setattr(config, key, value)
-
- config.validate()
- return config
-
- def save_to_file(self, file_path: Union[str, Path]) -> None:
- """
- Save configuration to JSON file.
-
- Args:
- file_path: Path to save configuration file
-
- Raises:
- ConfigurationFileError: If file cannot be saved
- """
- try:
- file_path = Path(file_path)
- file_path.parent.mkdir(parents=True, exist_ok=True)
-
- with open(file_path, 'w') as f:
- json.dump(self.to_dict(), f, indent=2, sort_keys=True)
-
- pprint(f"Configuration saved to {file_path}")
-
- except Exception as e:
- raise ConfigurationFileError(f"Failed to save configuration: {e}")
-
- @classmethod
- def load_from_file(cls, file_path: Union[str, Path]) -> 'ArbitrationConfig':
- """
- Load configuration from JSON file.
-
- Args:
- file_path: Path to configuration file
-
- Returns:
- ArbitrationConfig: Loaded configuration
-
- Raises:
- ConfigurationFileError: If file cannot be loaded
- """
- try:
- with open(file_path, 'r') as f:
- data = json.load(f)
-
- config = cls.from_dict(data)
- pprint(f"Configuration loaded from {file_path}")
- return config
-
- except FileNotFoundError:
- raise ConfigurationFileError(f"Configuration file not found: {file_path}")
- except json.JSONDecodeError as e:
- raise ConfigurationFileError(f"Invalid JSON in configuration file: {e}")
- except Exception as e:
- raise ConfigurationFileError(f"Failed to load configuration: {e}")
-
- def update_from_dict(self, updates: Dict[str, Any]) -> None:
- """
- Update configuration with new values.
-
- Args:
- updates: Dictionary of configuration updates
- """
- current_dict = self.to_dict()
- current_dict.update(updates)
-
- new_config = self.from_dict(current_dict)
-
- # Copy all attributes from new config
- for key, value in new_config.__dict__.items():
- setattr(self, key, value)
-
- pprint(f"Configuration updated with {len(updates)} changes")
-
- def reset_to_defaults(self) -> None:
- """Reset configuration to default values."""
- default_config = ArbitrationConfig()
-
- for key, value in default_config.__dict__.items():
- setattr(self, key, value)
-
- pprint("Configuration reset to defaults")
-
- def get_algorithm_config(self, algorithm: ArbitrationAlgorithm) -> Dict[str, Any]:
- """
- Get configuration specific to an algorithm.
-
- Args:
- algorithm: Algorithm to get configuration for
-
- Returns:
- Dict[str, Any]: Algorithm-specific configuration
- """
- if algorithm == ArbitrationAlgorithm.VOLUME_BASED:
- return {
- 'threshold': self.volume_threshold,
- 'hysteresis': self.volume_hysteresis,
- 'smoothing_factor': self.volume_smoothing_factor,
- }
- elif algorithm == ArbitrationAlgorithm.DISTANCE_BASED:
- return {
- 'threshold': self.distance_threshold,
- 'falloff': self.distance_falloff,
- 'use_estimated': self.use_estimated_distance,
- }
- elif algorithm == ArbitrationAlgorithm.PRIORITY_BASED:
- return {
- 'default_priority': self.default_satellite_priority,
- 'room_priorities': {
- room_id: settings.priority
- for room_id, settings in self.room_priorities.items()
- },
- }
- elif algorithm == ArbitrationAlgorithm.ROUND_ROBIN:
- return {
- 'reset_interval': self.round_robin_reset_interval,
- 'fairness_mode': self.round_robin_fairness_mode,
- }
- elif algorithm == ArbitrationAlgorithm.HYBRID:
- return {
- 'weights': self.algorithm_weights.to_dict(),
- }
- elif algorithm == ArbitrationAlgorithm.CUSTOM:
- return {
- 'plugin': self.custom_algorithm_plugin,
- 'config': self.custom_algorithm_config,
- }
- else:
- return {}
- def create_default_config() -> ArbitrationConfig:
- """
- Create a default arbitration configuration.
-
- Returns:
- ArbitrationConfig: Default configuration instance
- """
- config = ArbitrationConfig()
- pprint("Created default arbitration configuration")
- return config
- def load_config_from_file(file_path: Union[str, Path]) -> ArbitrationConfig:
- """
- Load arbitration configuration from file.
-
- Args:
- file_path: Path to configuration file
-
- Returns:
- ArbitrationConfig: Loaded configuration
- """
- return ArbitrationConfig.load_from_file(file_path)
- def create_config_with_overrides(**overrides) -> ArbitrationConfig:
- """
- Create configuration with specific overrides.
-
- Args:
- **overrides: Configuration overrides
-
- Returns:
- ArbitrationConfig: Configuration with overrides applied
- """
- config = ArbitrationConfig()
- config.update_from_dict(overrides)
- return config
|