event_data.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. """
  2. Event data structures for the Trixy application.
  3. This module defines the data classes that encapsulate information passed with events
  4. throughout the Trixy system. All event data classes inherit from TrixyEventData
  5. and provide structured, type-safe event information.
  6. """
  7. from abc import ABC
  8. from dataclasses import dataclass, field
  9. from datetime import datetime
  10. from typing import Any, Dict, List, Optional, Union
  11. from enum import Enum
  12. class EventType(Enum):
  13. """Enumeration of all supported Trixy event types."""
  14. # Satellite Management Events
  15. SATELLITE_CONNECTED = "satellite_connected"
  16. SATELLITE_DISCONNECTED = "satellite_disconnected"
  17. SATELLITE_REGISTERED = "satellite_registered"
  18. # Wakeword & Audio Events
  19. WAKEWORD_RECEIVED = "wakeword_received"
  20. RAW_AUDIO_INPUT_RECEIVED = "raw_audio_input_received"
  21. # Processing Events
  22. TEXT_RECEIVED = "text_received"
  23. INTENT_RECEIVED = "intent_received"
  24. TTS_RECEIVED = "tts_received"
  25. # Conversation Events
  26. CONVERSATION_STARTED = "conversation_started"
  27. CONVERSATION_ENDED = "conversation_ended"
  28. CONVERSATION_STATE_CHANGED = "conversation_state_changed"
  29. CONVERSATION_TURN_ADDED = "conversation_turn_added"
  30. CONVERSATION_CONTEXT_UPDATED = "conversation_context_updated"
  31. # Arbitration Events
  32. ARBITRATION_STARTED = "arbitration_started"
  33. ARBITRATION_COMPLETED = "arbitration_completed"
  34. ARBITRATION_CANCELLED = "arbitration_cancelled"
  35. ARBITRATION_TIMED_OUT = "arbitration_timed_out"
  36. ARBITRATION_REPORT_ADDED = "arbitration_report_added"
  37. SATELLITE_SELECTED = "satellite_selected"
  38. SATELLITE_IGNORED = "satellite_ignored"
  39. # System Events
  40. SYSTEM_STARTUP = "system_startup"
  41. SYSTEM_SHUTDOWN = "system_shutdown"
  42. PLUGIN_LOADED = "plugin_loaded"
  43. PLUGIN_UNLOADED = "plugin_unloaded"
  44. TRAINING_STARTED = "training_started"
  45. TRAINING_COMPLETED = "training_completed"
  46. SCHEDULE_TRIGGERED = "schedule_triggered"
  47. @dataclass
  48. class TrixyEventData(ABC):
  49. """
  50. Base class for all Trixy event data.
  51. All event data classes should inherit from this class to provide
  52. consistent timestamp tracking and metadata support.
  53. """
  54. timestamp: datetime = field(default_factory=datetime.now)
  55. metadata: Dict[str, Any] = field(default_factory=dict)
  56. def to_dict(self) -> Dict[str, Any]:
  57. """Convert event data to dictionary representation."""
  58. result = {}
  59. for key, value in self.__dict__.items():
  60. if isinstance(value, datetime):
  61. result[key] = value.isoformat()
  62. elif hasattr(value, 'to_dict'):
  63. result[key] = value.to_dict()
  64. else:
  65. result[key] = value
  66. return result
  67. @dataclass
  68. class SatelliteInfo:
  69. """Information about a satellite device."""
  70. satellite_id: str
  71. mac_address: str
  72. room_id: str
  73. alias: str
  74. version: str
  75. audio_ports: Dict[str, int] = field(default_factory=dict)
  76. capabilities: List[str] = field(default_factory=list)
  77. def to_dict(self) -> Dict[str, Any]:
  78. """Convert satellite info to dictionary."""
  79. return {
  80. 'satellite_id': self.satellite_id,
  81. 'mac_address': self.mac_address,
  82. 'room_id': self.room_id,
  83. 'alias': self.alias,
  84. 'version': self.version,
  85. 'audio_ports': self.audio_ports,
  86. 'capabilities': self.capabilities
  87. }
  88. @dataclass
  89. class SpeakerInfo:
  90. """Information about a detected speaker."""
  91. speaker_id: str
  92. speaker_name: str
  93. confidence: float = 0.0
  94. embedding: Optional[List[float]] = None
  95. def to_dict(self) -> Dict[str, Any]:
  96. """Convert speaker info to dictionary."""
  97. return {
  98. 'speaker_id': self.speaker_id,
  99. 'speaker_name': self.speaker_name,
  100. 'confidence': self.confidence,
  101. 'embedding': self.embedding
  102. }
  103. # Satellite Management Event Data Classes
  104. @dataclass
  105. class SatelliteConnectedEventData(TrixyEventData):
  106. """Data for satellite_connected event."""
  107. satellite_info: SatelliteInfo = field(default=None)
  108. connection_time: datetime = field(default_factory=datetime.now)
  109. server_assigned_ports: Dict[str, int] = field(default_factory=dict)
  110. @dataclass
  111. class SatelliteDisconnectedEventData(TrixyEventData):
  112. """Data for satellite_disconnected event."""
  113. satellite_id: str = field(default="")
  114. satellite_info: Optional[SatelliteInfo] = None
  115. disconnection_reason: str = "unknown"
  116. was_clean_disconnect: bool = False
  117. @dataclass
  118. class SatelliteRegisteredEventData(TrixyEventData):
  119. """Data for satellite_registered event."""
  120. satellite_info: SatelliteInfo = field(default=None)
  121. registration_file_path: str = field(default="")
  122. registration_mode_timeout: int = 60
  123. # Wakeword & Audio Event Data Classes
  124. @dataclass
  125. class WakewordReceivedEventData(TrixyEventData):
  126. """Data for wakeword_received event."""
  127. wakeword_id: str = field(default="")
  128. wakeword_type: str = field(default="") # "custom", "system_command"
  129. speaker_info: SpeakerInfo = field(default=None)
  130. satellite_info: SatelliteInfo = field(default=None)
  131. volume: float = field(default=0.0)
  132. confidence: float = 0.0
  133. raw_scores: Dict[str, float] = field(default_factory=dict)
  134. processing_time_ms: float = 0.0
  135. audio_buffer_length: float = 0.0 # seconds
  136. chunk_id: int = 0
  137. features_shape: List[int] = field(default_factory=list)
  138. model_name: str = field(default="")
  139. temporal_filtered: bool = False
  140. @dataclass
  141. class RawAudioInputReceivedEventData(TrixyEventData):
  142. """Data for raw_audio_input_received event."""
  143. conversation_id: str = field(default="")
  144. audio_data: bytes = field(default=b"")
  145. speaker_info: SpeakerInfo = field(default=None)
  146. satellite_info: SatelliteInfo = field(default=None)
  147. sample_rate: int = 16000
  148. channels: int = 1
  149. bit_depth: int = 16
  150. duration_seconds: float = 0.0
  151. # Processing Event Data Classes
  152. @dataclass
  153. class TextReceivedEventData(TrixyEventData):
  154. """Data for text_received event."""
  155. conversation_id: str = field(default="")
  156. text: str = field(default="")
  157. confidence: float = field(default=0.0)
  158. speaker_info: SpeakerInfo = field(default=None)
  159. satellite_info: Optional[SatelliteInfo] = None
  160. language_code: str = "en-US"
  161. processing_time_ms: float = 0.0
  162. @dataclass
  163. class IntentReceivedEventData(TrixyEventData):
  164. """Data for intent_received event."""
  165. conversation_id: str = field(default="")
  166. intent: str = field(default="")
  167. entities: Dict[str, Any] = field(default_factory=dict)
  168. confidence: float = field(default=0.0)
  169. original_text: str = field(default="")
  170. speaker_info: SpeakerInfo = field(default=None)
  171. satellite_info: Optional[SatelliteInfo] = None
  172. processing_time_ms: float = 0.0
  173. @dataclass
  174. class TTSReceivedEventData(TrixyEventData):
  175. """Data for tts_received event."""
  176. conversation_id: str = field(default="")
  177. audio_data: bytes = field(default=b"")
  178. text: str = field(default="")
  179. voice_settings: Dict[str, Any] = field(default_factory=dict)
  180. sample_rate: int = 16000
  181. channels: int = 1
  182. bit_depth: int = 16
  183. duration_seconds: float = 0.0
  184. target_satellite_id: Optional[str] = None
  185. # System Event Data Classes
  186. @dataclass
  187. class SystemStartupEventData(TrixyEventData):
  188. """Data for system_startup event."""
  189. mode: str = field(default="") # "server", "client", "standalone"
  190. version: str = field(default="")
  191. config_file: str = field(default="")
  192. debug_mode: bool = False
  193. startup_time_ms: float = 0.0
  194. @dataclass
  195. class SystemShutdownEventData(TrixyEventData):
  196. """Data for system_shutdown event."""
  197. reason: str = "user_requested"
  198. graceful_shutdown: bool = True
  199. uptime_seconds: float = 0.0
  200. @dataclass
  201. class PluginEventData(TrixyEventData):
  202. """Base data for plugin-related events."""
  203. plugin_name: str = field(default="")
  204. plugin_path: str = field(default="")
  205. plugin_version: str = "unknown"
  206. config_data: Dict[str, Any] = field(default_factory=dict)
  207. @dataclass
  208. class PluginLoadedEventData(PluginEventData):
  209. """Data for plugin_loaded event."""
  210. load_time_ms: float = 0.0
  211. enabled: bool = True
  212. @dataclass
  213. class PluginUnloadedEventData(PluginEventData):
  214. """Data for plugin_unloaded event."""
  215. unload_reason: str = "user_requested"
  216. was_enabled: bool = False
  217. @dataclass
  218. class TrainingStartedEventData(TrixyEventData):
  219. """Data for training_started event."""
  220. training_type: str = field(default="") # "wakeword", "voice_recognition"
  221. model_name: str = field(default="")
  222. dataset_size: int = field(default=0)
  223. estimated_duration_minutes: float = 0.0
  224. training_parameters: Dict[str, Any] = field(default_factory=dict)
  225. @dataclass
  226. class TrainingCompletedEventData(TrixyEventData):
  227. """Data for training_completed event."""
  228. training_type: str = field(default="")
  229. model_name: str = field(default="")
  230. success: bool = field(default=False)
  231. final_accuracy: float = 0.0
  232. training_duration_minutes: float = 0.0
  233. model_save_path: str = ""
  234. error_message: str = ""
  235. @dataclass
  236. class ScheduleTriggeredEventData(TrixyEventData):
  237. """Data for schedule_triggered event."""
  238. schedule_id: str = field(default="")
  239. schedule_name: str = field(default="")
  240. trigger_type: str = field(default="") # "date", "time", "event", "weekday"
  241. trigger_value: str = field(default="")
  242. action_type: str = field(default="") # "trigger_event", "start_training", "call_function"
  243. action_data: Dict[str, Any] = field(default_factory=dict)
  244. next_trigger_time: Optional[datetime] = None
  245. # Conversation Event Data Classes
  246. @dataclass
  247. class ConversationStartedEventData(TrixyEventData):
  248. """Data for conversation_started event."""
  249. conversation_id: str = field(default="")
  250. trigger_event: str = field(default="")
  251. speaker_info: Optional[SpeakerInfo] = None
  252. satellite_info: Optional[SatelliteInfo] = None
  253. timeout_seconds: float = 300.0
  254. max_turns: int = 50
  255. @dataclass
  256. class ConversationEndedEventData(TrixyEventData):
  257. """Data for conversation_ended event."""
  258. conversation_id: str = field(default="")
  259. end_reason: str = field(default="") # "completed", "timeout", "cancelled", "error"
  260. final_state: str = field(default="")
  261. duration_seconds: float = 0.0
  262. turn_count: int = 0
  263. speaker_info: Optional[SpeakerInfo] = None
  264. satellite_info: Optional[SatelliteInfo] = None
  265. final_response: Optional[str] = None
  266. error_message: Optional[str] = None
  267. @dataclass
  268. class ConversationStateChangedEventData(TrixyEventData):
  269. """Data for conversation_state_changed event."""
  270. conversation_id: str = field(default="")
  271. from_state: str = field(default="")
  272. to_state: str = field(default="")
  273. trigger: str = field(default="")
  274. source: str = field(default="")
  275. duration_ms: float = 0.0
  276. metadata: Dict[str, Any] = field(default_factory=dict)
  277. @dataclass
  278. class ConversationTurnAddedEventData(TrixyEventData):
  279. """Data for conversation_turn_added event."""
  280. conversation_id: str = field(default="")
  281. turn_id: str = field(default="")
  282. turn_number: int = 0
  283. role: str = field(default="") # "user", "assistant", "system", "plugin"
  284. input_type: str = field(default="")
  285. input_text: Optional[str] = None
  286. input_confidence: float = 0.0
  287. speaker_info: Optional[SpeakerInfo] = None
  288. satellite_info: Optional[SatelliteInfo] = None
  289. plugin_name: Optional[str] = None
  290. @dataclass
  291. class ConversationContextUpdatedEventData(TrixyEventData):
  292. """Data for conversation_context_updated event."""
  293. conversation_id: str = field(default="")
  294. context_key: str = field(default="")
  295. context_scope: str = field(default="") # "session", "speaker", "satellite", "room", "global", "plugin"
  296. operation: str = field(default="") # "set", "update", "delete", "clear"
  297. source: str = field(default="")
  298. metadata: Dict[str, Any] = field(default_factory=dict)
  299. # Arbitration Event Data Classes
  300. @dataclass
  301. class ArbitrationStartedEventData(TrixyEventData):
  302. """Data for arbitration_started event."""
  303. arbitration_session_id: str = field(default="")
  304. trigger_satellite_id: str = field(default="")
  305. trigger_wakeword_id: str = field(default="")
  306. collection_window_seconds: float = 1.0
  307. algorithm: str = field(default="volume_based")
  308. expected_participants: int = 1
  309. @dataclass
  310. class ArbitrationCompletedEventData(TrixyEventData):
  311. """Data for arbitration_completed event."""
  312. arbitration_session_id: str = field(default="")
  313. selected_satellite_id: str = field(default="")
  314. selection_algorithm: str = field(default="")
  315. selection_score: float = 0.0
  316. selection_confidence: float = 0.0
  317. total_participants: int = 0
  318. collection_duration_ms: float = 0.0
  319. arbitration_duration_ms: float = 0.0
  320. conversation_id: Optional[str] = None
  321. selection_reason: str = field(default="")
  322. alternative_selections: Dict[str, float] = field(default_factory=dict)
  323. @dataclass
  324. class ArbitrationCancelledEventData(TrixyEventData):
  325. """Data for arbitration_cancelled event."""
  326. arbitration_session_id: str = field(default="")
  327. cancellation_reason: str = field(default="")
  328. participants_count: int = 0
  329. duration_ms: float = 0.0
  330. cancelled_by: str = field(default="system")
  331. @dataclass
  332. class ArbitrationTimedOutEventData(TrixyEventData):
  333. """Data for arbitration_timed_out event."""
  334. arbitration_session_id: str = field(default="")
  335. timeout_duration_ms: float = 0.0
  336. participants_count: int = 0
  337. reports_received: int = 0
  338. timeout_phase: str = field(default="") # "collection", "arbitration", "session"
  339. @dataclass
  340. class ArbitrationReportAddedEventData(TrixyEventData):
  341. """Data for arbitration_report_added event."""
  342. arbitration_session_id: str = field(default="")
  343. satellite_id: str = field(default="")
  344. wakeword_id: str = field(default="")
  345. volume: float = 0.0
  346. confidence: float = 0.0
  347. speaker_info: Optional[SpeakerInfo] = None
  348. satellite_info: Optional[SatelliteInfo] = None
  349. report_timestamp: datetime = field(default_factory=datetime.now)
  350. estimated_distance: Optional[float] = None
  351. room_id: str = field(default="")
  352. total_reports_in_session: int = 1
  353. @dataclass
  354. class SatelliteSelectedEventData(TrixyEventData):
  355. """Data for satellite_selected event."""
  356. arbitration_session_id: str = field(default="")
  357. selected_satellite_id: str = field(default="")
  358. satellite_info: Optional[SatelliteInfo] = None
  359. selection_algorithm: str = field(default="")
  360. selection_score: float = 0.0
  361. selection_confidence: float = 0.0
  362. conversation_id: str = field(default="")
  363. selection_metadata: Dict[str, Any] = field(default_factory=dict)
  364. @dataclass
  365. class SatelliteIgnoredEventData(TrixyEventData):
  366. """Data for satellite_ignored event."""
  367. arbitration_session_id: str = field(default="")
  368. ignored_satellite_id: str = field(default="")
  369. satellite_info: Optional[SatelliteInfo] = None
  370. selected_satellite_id: str = field(default="")
  371. ignore_reason: str = field(default="not_selected")
  372. original_report_volume: float = 0.0
  373. original_report_confidence: float = 0.0
  374. # Event Data Factory
  375. class EventDataFactory:
  376. """Factory class for creating event data instances."""
  377. _event_data_classes = {
  378. EventType.SATELLITE_CONNECTED: SatelliteConnectedEventData,
  379. EventType.SATELLITE_DISCONNECTED: SatelliteDisconnectedEventData,
  380. EventType.SATELLITE_REGISTERED: SatelliteRegisteredEventData,
  381. EventType.WAKEWORD_RECEIVED: WakewordReceivedEventData,
  382. EventType.RAW_AUDIO_INPUT_RECEIVED: RawAudioInputReceivedEventData,
  383. EventType.TEXT_RECEIVED: TextReceivedEventData,
  384. EventType.INTENT_RECEIVED: IntentReceivedEventData,
  385. EventType.TTS_RECEIVED: TTSReceivedEventData,
  386. EventType.CONVERSATION_STARTED: ConversationStartedEventData,
  387. EventType.CONVERSATION_ENDED: ConversationEndedEventData,
  388. EventType.CONVERSATION_STATE_CHANGED: ConversationStateChangedEventData,
  389. EventType.CONVERSATION_TURN_ADDED: ConversationTurnAddedEventData,
  390. EventType.CONVERSATION_CONTEXT_UPDATED: ConversationContextUpdatedEventData,
  391. EventType.ARBITRATION_STARTED: ArbitrationStartedEventData,
  392. EventType.ARBITRATION_COMPLETED: ArbitrationCompletedEventData,
  393. EventType.ARBITRATION_CANCELLED: ArbitrationCancelledEventData,
  394. EventType.ARBITRATION_TIMED_OUT: ArbitrationTimedOutEventData,
  395. EventType.ARBITRATION_REPORT_ADDED: ArbitrationReportAddedEventData,
  396. EventType.SATELLITE_SELECTED: SatelliteSelectedEventData,
  397. EventType.SATELLITE_IGNORED: SatelliteIgnoredEventData,
  398. EventType.SYSTEM_STARTUP: SystemStartupEventData,
  399. EventType.SYSTEM_SHUTDOWN: SystemShutdownEventData,
  400. EventType.PLUGIN_LOADED: PluginLoadedEventData,
  401. EventType.PLUGIN_UNLOADED: PluginUnloadedEventData,
  402. EventType.TRAINING_STARTED: TrainingStartedEventData,
  403. EventType.TRAINING_COMPLETED: TrainingCompletedEventData,
  404. EventType.SCHEDULE_TRIGGERED: ScheduleTriggeredEventData,
  405. }
  406. @classmethod
  407. def create_event_data(cls, event_type: Union[EventType, str], **kwargs) -> TrixyEventData:
  408. """
  409. Create an event data instance for the specified event type.
  410. Args:
  411. event_type: The type of event (EventType enum or string)
  412. **kwargs: Arguments to pass to the event data constructor
  413. Returns:
  414. TrixyEventData: An instance of the appropriate event data class
  415. Raises:
  416. ValueError: If the event type is not supported
  417. """
  418. if isinstance(event_type, str):
  419. try:
  420. event_type = EventType(event_type)
  421. except ValueError:
  422. raise ValueError(f"Unknown event type: {event_type}")
  423. if event_type not in cls._event_data_classes:
  424. raise ValueError(f"No event data class registered for event type: {event_type}")
  425. event_data_class = cls._event_data_classes[event_type]
  426. return event_data_class(**kwargs)
  427. @classmethod
  428. def get_supported_events(cls) -> List[EventType]:
  429. """Get a list of all supported event types."""
  430. return list(cls._event_data_classes.keys())
  431. @classmethod
  432. def is_supported_event(cls, event_type: Union[EventType, str]) -> bool:
  433. """Check if an event type is supported."""
  434. if isinstance(event_type, str):
  435. try:
  436. event_type = EventType(event_type)
  437. except ValueError:
  438. return False
  439. return event_type in cls._event_data_classes