main.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. """
  2. Example Plugin for Trixy Application
  3. This plugin demonstrates the basic functionality of the Trixy plugin system:
  4. - Basic plugin structure and inheritance from TrixyPlugin
  5. - Event handling with @TrixyEvent decorator
  6. - Configuration management with auto-loading and type-casting
  7. - Plugin lifecycle management (initialize, enable, disable, cleanup)
  8. - Integration with application container and event system
  9. - Basic health monitoring and error handling
  10. This serves as a template and reference for developing new plugins.
  11. Features demonstrated:
  12. - Multiple event handlers
  13. - Configuration with different data types
  14. - Plugin state management
  15. - Error handling and recovery
  16. - Health status reporting
  17. - Custom plugin methods
  18. """
  19. from typing import Any, Dict, Optional
  20. import time
  21. import random
  22. # Import plugin base class and event decorator
  23. from trixy_core.plugins import TrixyPlugin
  24. from trixy_core.events import TrixyEvent
  25. def pprint(message: str) -> None:
  26. """Example plugin logging function."""
  27. print(f"[EXAMPLE_PLUGIN] {message}")
  28. class ExamplePlugin(TrixyPlugin):
  29. """
  30. Example plugin demonstrating basic Trixy plugin functionality.
  31. This plugin:
  32. - Responds to various system events
  33. - Maintains configuration settings
  34. - Demonstrates plugin lifecycle management
  35. - Shows event handling patterns
  36. - Provides health monitoring example
  37. """
  38. def initialize(self) -> None:
  39. """Initialize the example plugin."""
  40. pprint("Initializing Example Plugin...")
  41. # Plugin-specific initialization
  42. self._message_count = 0
  43. self._last_wakeword_time = None
  44. self._last_text_received = None
  45. self._startup_time = time.time()
  46. # Configuration validation and setup
  47. self._setup_configuration()
  48. # Plugin health status
  49. self._health_status = "initialized"
  50. pprint(f"Example Plugin initialized with config: {self.config}")
  51. def _setup_configuration(self) -> None:
  52. """Set up plugin configuration with defaults and validation."""
  53. # Get configuration values with type casting and defaults
  54. self.greeting_message = self.get_config_value(
  55. "greeting_message",
  56. default="Hello from Example Plugin!",
  57. type_cast=str
  58. )
  59. self.response_enabled = self.get_config_value(
  60. "response_enabled",
  61. default=True,
  62. type_cast=bool
  63. )
  64. self.response_delay = self.get_config_value(
  65. "response_delay",
  66. default=1.0,
  67. type_cast=float
  68. )
  69. self.max_responses_per_session = self.get_config_value(
  70. "max_responses_per_session",
  71. default=5,
  72. type_cast=int
  73. )
  74. self.allowed_speakers = self.get_config_value(
  75. "allowed_speakers",
  76. default=["all"],
  77. type_cast=list
  78. )
  79. # Validate configuration
  80. if self.response_delay < 0:
  81. pprint("Warning: response_delay cannot be negative, setting to 0")
  82. self.set_config_value("response_delay", 0.0)
  83. self.response_delay = 0.0
  84. if self.max_responses_per_session < 1:
  85. pprint("Warning: max_responses_per_session must be at least 1")
  86. self.set_config_value("max_responses_per_session", 1)
  87. self.max_responses_per_session = 1
  88. # Event Handlers
  89. @TrixyEvent("system_startup")
  90. def on_system_startup(self, event_name: str, event_data: Any) -> None:
  91. """Handle system startup event."""
  92. if not self.is_enabled():
  93. return
  94. pprint("System startup detected!")
  95. pprint(f"Startup mode: {event_data.get('mode', 'unknown')}")
  96. pprint(f"Debug mode: {event_data.get('debug_mode', False)}")
  97. self._health_status = "running"
  98. # Example: Send a greeting message
  99. if self.response_enabled:
  100. try:
  101. # Get event handler to trigger a custom event
  102. event_handler = self.application.get_event_handler()
  103. if event_handler:
  104. greeting_data = {
  105. "message": self.greeting_message,
  106. "plugin": "example_plugin",
  107. "timestamp": time.time()
  108. }
  109. event_handler.trigger_event("example_greeting", greeting_data)
  110. except Exception as e:
  111. pprint(f"Error sending greeting: {e}")
  112. @TrixyEvent(["wakeword_received", "text_received"])
  113. def handle_voice_events(self, event_name: str, event_data: Any) -> None:
  114. """Handle wakeword and text events."""
  115. if not self.is_enabled():
  116. return
  117. try:
  118. if event_name == "wakeword_received":
  119. self._handle_wakeword(event_data)
  120. elif event_name == "text_received":
  121. self._handle_text_received(event_data)
  122. except Exception as e:
  123. pprint(f"Error handling voice event {event_name}: {e}")
  124. def _handle_wakeword(self, event_data: Any) -> None:
  125. """Handle wakeword detection."""
  126. speaker_name = event_data.get('speaker_name', 'unknown')
  127. satellite_id = event_data.get('satellite_id', 'unknown')
  128. volume = event_data.get('volume', 0)
  129. pprint(f"Wakeword detected! Speaker: {speaker_name}, Satellite: {satellite_id}, Volume: {volume}")
  130. # Check if speaker is allowed
  131. if not self._is_speaker_allowed(speaker_name):
  132. pprint(f"Speaker {speaker_name} not in allowed list, ignoring")
  133. return
  134. self._last_wakeword_time = time.time()
  135. self._message_count += 1
  136. # Example: Log wakeword statistics
  137. pprint(f"Total wakewords processed: {self._message_count}")
  138. def _handle_text_received(self, event_data: Any) -> None:
  139. """Handle text from speech-to-text."""
  140. text = event_data.get('text', '')
  141. confidence = event_data.get('confidence', 0.0)
  142. speaker_name = event_data.get('speaker_info', {}).get('name', 'unknown')
  143. pprint(f"Text received: '{text}' (confidence: {confidence:.2f}) from {speaker_name}")
  144. # Check if speaker is allowed
  145. if not self._is_speaker_allowed(speaker_name):
  146. pprint(f"Speaker {speaker_name} not in allowed list, ignoring")
  147. return
  148. self._last_text_received = text
  149. # Example: Simple command recognition
  150. if text.lower().startswith("example"):
  151. self._handle_example_command(text, event_data)
  152. def _handle_example_command(self, text: str, event_data: Any) -> None:
  153. """Handle commands that start with 'example'."""
  154. if not self.response_enabled:
  155. pprint("Response disabled, ignoring command")
  156. return
  157. # Check session limit
  158. if self._message_count > self.max_responses_per_session:
  159. pprint("Maximum responses per session reached, ignoring command")
  160. return
  161. pprint(f"Handling example command: {text}")
  162. # Add response delay if configured
  163. if self.response_delay > 0:
  164. time.sleep(self.response_delay)
  165. # Generate a response
  166. responses = [
  167. "Example plugin is working!",
  168. f"Hello! I received: {text}",
  169. "Example plugin at your service!",
  170. f"Message count: {self._message_count}",
  171. "This is an example response!"
  172. ]
  173. response = random.choice(responses)
  174. try:
  175. # Trigger a response event
  176. event_handler = self.application.get_event_handler()
  177. if event_handler:
  178. response_data = {
  179. "text": response,
  180. "plugin": "example_plugin",
  181. "original_command": text,
  182. "timestamp": time.time()
  183. }
  184. event_handler.trigger_event("example_response", response_data)
  185. pprint(f"Triggered response: {response}")
  186. except Exception as e:
  187. pprint(f"Error triggering response: {e}")
  188. @TrixyEvent("system_shutdown")
  189. def on_system_shutdown(self, event_name: str, event_data: Any) -> None:
  190. """Handle system shutdown event."""
  191. pprint("System shutdown detected, saving statistics...")
  192. # Example: Save plugin statistics
  193. uptime = time.time() - self._startup_time
  194. stats = {
  195. "uptime_seconds": uptime,
  196. "messages_processed": self._message_count,
  197. "last_activity": self._last_wakeword_time or self._startup_time
  198. }
  199. # Save statistics to config for persistence
  200. self.set_config_value("last_session_stats", stats)
  201. pprint(f"Example plugin shutting down after {uptime:.1f} seconds")
  202. # Lifecycle Hooks
  203. def on_enable(self) -> None:
  204. """Called when the plugin is enabled."""
  205. pprint("Example plugin enabled")
  206. self._health_status = "enabled"
  207. # Reset counters on enable
  208. self._message_count = 0
  209. self._startup_time = time.time()
  210. def on_disable(self) -> None:
  211. """Called when the plugin is disabled."""
  212. pprint("Example plugin disabled")
  213. self._health_status = "disabled"
  214. def cleanup(self) -> None:
  215. """Clean up plugin resources."""
  216. pprint("Example plugin cleanup")
  217. # Save final statistics
  218. if hasattr(self, '_startup_time'):
  219. uptime = time.time() - self._startup_time
  220. final_stats = {
  221. "total_uptime_seconds": uptime,
  222. "total_messages_processed": self._message_count,
  223. "last_cleanup": time.time()
  224. }
  225. self.set_config_value("cleanup_stats", final_stats)
  226. self._health_status = "cleanup_complete"
  227. # Plugin-Specific Methods
  228. def _is_speaker_allowed(self, speaker_name: str) -> bool:
  229. """Check if a speaker is in the allowed list."""
  230. if "all" in self.allowed_speakers:
  231. return True
  232. return speaker_name in self.allowed_speakers
  233. def get_plugin_stats(self) -> Dict[str, Any]:
  234. """Get plugin statistics."""
  235. uptime = time.time() - self._startup_time if hasattr(self, '_startup_time') else 0
  236. return {
  237. "plugin_name": "example_plugin",
  238. "enabled": self.is_enabled(),
  239. "uptime_seconds": uptime,
  240. "messages_processed": self._message_count,
  241. "last_wakeword_time": self._last_wakeword_time,
  242. "last_text_received": self._last_text_received,
  243. "health_status": getattr(self, '_health_status', 'unknown'),
  244. "configuration": {
  245. "greeting_message": self.greeting_message,
  246. "response_enabled": self.response_enabled,
  247. "response_delay": self.response_delay,
  248. "max_responses_per_session": self.max_responses_per_session,
  249. "allowed_speakers": self.allowed_speakers
  250. }
  251. }
  252. def reset_counters(self) -> None:
  253. """Reset plugin counters (utility method)."""
  254. self._message_count = 0
  255. self._last_wakeword_time = None
  256. self._last_text_received = None
  257. pprint("Plugin counters reset")
  258. def simulate_error(self) -> None:
  259. """Simulate an error for testing error handling."""
  260. pprint("Simulating error for testing...")
  261. raise Exception("This is a simulated error for testing plugin error handling")
  262. # Override health check to provide plugin-specific health info
  263. def check_health(self) -> Dict[str, Any]:
  264. """Check plugin health with custom status."""
  265. # Get base health info
  266. health = super().check_health()
  267. # Add plugin-specific health information
  268. health.update({
  269. 'custom_health_status': getattr(self, '_health_status', 'unknown'),
  270. 'message_count': self._message_count,
  271. 'last_activity': self._last_wakeword_time,
  272. 'uptime': time.time() - self._startup_time if hasattr(self, '_startup_time') else 0,
  273. 'configuration_valid': self._validate_current_config(),
  274. 'response_capability': self.response_enabled and self.is_enabled()
  275. })
  276. return health
  277. def _validate_current_config(self) -> bool:
  278. """Validate current configuration."""
  279. try:
  280. # Check all required config values are present and valid
  281. required_keys = ['greeting_message', 'response_enabled', 'response_delay', 'max_responses_per_session', 'allowed_speakers']
  282. for key in required_keys:
  283. if not hasattr(self, key):
  284. return False
  285. # Check value ranges
  286. if self.response_delay < 0 or self.max_responses_per_session < 1:
  287. return False
  288. return True
  289. except:
  290. return False