Walked the README and docs/ tree against current code and fixed several
real bugs and many stale references. Highlights:
User-facing
- README.md: web interface install instructions referenced
install_web_service.sh at the repo root, but it actually lives at
scripts/install/install_web_service.sh.
- docs/GETTING_STARTED.md: every web UI port reference said 5050, but
the real server in web_interface/start.py:123 binds 5000. Same bug
was duplicated in docs/TROUBLESHOOTING.md (17 occurrences). Fixed
both.
- docs/GETTING_STARTED.md: rewrote tab-by-tab instructions. The doc
referenced "Plugin Store", "Plugin Management", "Sports Configuration",
"Durations", and "Font Management" tabs - none of which exist. Real
tabs (verified in web_interface/templates/v3/base.html) are: Overview,
General, WiFi, Schedule, Display, Config Editor, Fonts, Logs, Cache,
Operation History, Plugin Manager (+ per-plugin tabs).
- docs/GETTING_STARTED.md: removed references to a "Test Display"
button (doesn't exist) and "Show Now" / "Stop" plugin buttons. Real
controls are "Run On-Demand" / "Stop On-Demand" inside each plugin's
tab (partials/plugin_config.html:792).
- docs/TROUBLESHOOTING.md: removed dead reference to
troubleshoot_weather.sh (doesn't exist anywhere in the repo); weather
is now a plugin in ledmatrix-plugins.
Developer-facing
- docs/PLUGIN_API_REFERENCE.md: documented draw_image() doesn't exist
on DisplayManager. Real plugins paste onto display_manager.image
directly (verified in src/base_classes/{baseball,basketball,football,
hockey}.py). Replaced with the canonical pattern.
- docs/PLUGIN_API_REFERENCE.md: documented cache_manager.delete() doesn't
exist. Real method is clear_cache(key=None). Updated the section.
- docs/PLUGIN_API_REFERENCE.md: added 10 missing BasePlugin methods that
the doc never mentioned: dynamic-duration hooks, live-priority hooks,
and the full Vegas-mode interface.
- docs/PLUGIN_DEVELOPMENT_GUIDE.md: same draw_image fix.
- docs/DEVELOPMENT.md: corrected the "Plugin Submodules" section. Plugins
are NOT git submodules - .gitmodules only contains
rpi-rgb-led-matrix-master. Plugins are installed at runtime into the
plugins directory configured by plugin_system.plugins_directory
(default plugin-repos/). Both internal links in this doc were also
broken (missing relative path adjustment).
- docs/HOW_TO_RUN_TESTS.md: removed pytest-timeout from install line
(not in requirements.txt) and corrected the test/integration/ path
(real integration tests are at test/web_interface/integration/).
Replaced the fictional file structure diagram with the real one.
- docs/EMULATOR_SETUP_GUIDE.md: clone URL was a placeholder; default
pixel_size was documented as 16 but emulator_config.json ships with 5.
Index
- docs/README.md: rewrote. Old index claimed "16-17 files after
consolidation" but docs/ actually has 38 .md files. Four were missing
from the index entirely (CONFIG_DEBUGGING, DEV_PREVIEW,
PLUGIN_ERROR_HANDLING, STARLARK_APPS_GUIDE). Trimmed the navel-gazing
consolidation/statistics sections.
Out of scope but worth flagging:
- src/plugin_system/resource_monitor.py:343 and src/common/api_helper.py:287
call cache_manager.delete(key) but no such method exists on
CacheManager. Both call sites would AttributeError at runtime if hit.
Not fixed in this docs PR - either add a delete() shim or convert
callers to clear_cache().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
26 KiB
Plugin API Reference
Complete API reference for plugin developers. This document describes all methods and properties available to plugins through the Display Manager, Cache Manager, and Plugin Manager.
Table of Contents
BasePlugin
All plugins must inherit from BasePlugin and implement the required methods. The base class provides access to managers and common functionality.
Available Properties
self.plugin_id # Plugin identifier (string)
self.config # Plugin configuration dictionary
self.display_manager # DisplayManager instance
self.cache_manager # CacheManager instance
self.plugin_manager # PluginManager instance
self.logger # Plugin-specific logger
self.enabled # Boolean enabled status
Required Methods
update() -> None
Fetch/update data for this plugin. Called based on update_interval specified in the plugin's manifest.
Example:
def update(self):
cache_key = f"{self.plugin_id}_data"
cached = self.cache_manager.get(cache_key, max_age=3600)
if cached:
self.data = cached
return
self.data = self._fetch_from_api()
self.cache_manager.set(cache_key, self.data)
display(force_clear: bool = False) -> None
Render this plugin's display. Called during display rotation or when explicitly requested.
Parameters:
force_clear(bool): If True, clear display before rendering
Example:
def display(self, force_clear=False):
if force_clear:
self.display_manager.clear()
self.display_manager.draw_text(
"Hello, World!",
x=5, y=15,
color=(255, 255, 255)
)
self.display_manager.update_display()
Optional Methods
validate_config() -> bool
Validate plugin configuration. Override to implement custom validation.
Returns: True if config is valid, False otherwise
has_live_content() -> bool
Check if plugin currently has live content. Override for live priority plugins.
Returns: True if plugin has live content
get_live_modes() -> List[str]
Get list of display modes to show during live priority takeover.
Returns: List of mode names
cleanup() -> None
Clean up resources when plugin is unloaded. Override to close connections, stop threads, etc.
on_config_change(new_config: Dict[str, Any]) -> None
Called after plugin configuration is updated via web API.
on_enable() -> None
Called when plugin is enabled.
on_disable() -> None
Called when plugin is disabled.
get_display_duration() -> float
Get display duration for this plugin. Can be overridden for dynamic durations.
Returns: Duration in seconds
get_info() -> Dict[str, Any]
Return plugin info for display in web UI. Override to provide additional state information.
Dynamic-duration hooks
Plugins that render multi-step content (e.g. cycling through several games)
can extend their display time until they've shown everything. To opt in,
either set dynamic_duration.enabled: true in the plugin's config or
override supports_dynamic_duration().
supports_dynamic_duration() -> bool
Return True if this plugin should use dynamic durations. Default reads
config["dynamic_duration"]["enabled"].
get_dynamic_duration_cap() -> Optional[float]
Maximum number of seconds the controller will keep this plugin on screen
in dynamic mode. Default reads
config["dynamic_duration"]["max_duration_seconds"].
is_cycle_complete() -> bool
Override this to return True only after the plugin has rendered all of
its content for the current rotation. Default returns True immediately,
which means a single display() call counts as a full cycle.
reset_cycle_state() -> None
Called by the controller before each new dynamic-duration session. Reset internal counters/iterators here.
Live priority hooks
Live priority lets a plugin temporarily take over the rotation when it has
urgent content (live games, breaking news). Enable by setting
live_priority: true in the plugin's config and overriding
has_live_content().
has_live_priority() -> bool
Whether live priority is enabled in config (default reads
config["live_priority"]).
has_live_content() -> bool
Override to return True when the plugin currently has urgent content.
Default returns False.
get_live_modes() -> List[str]
List of display modes to show during a live takeover. Default returns the
plugin's display_modes from its manifest.
Vegas scroll hooks
Vegas mode shows multiple plugins as a single continuous scroll instead of rotating one at a time. Plugins control how their content appears via these hooks. See ADVANCED_FEATURES.md for the user side of Vegas mode.
get_vegas_content() -> Optional[PIL.Image | List[PIL.Image] | None]
Return content to inject into the scroll. Multi-item plugins (sports,
odds, news) should return a list of PIL Images so each item scrolls
independently. Static plugins (clock, weather) can return a single image.
Returning None falls back to capturing whatever display() produces.
get_vegas_content_type() -> str
'multi', 'static', or 'none'. Affects how Vegas mode treats the
plugin. Default 'static'.
get_vegas_display_mode() -> VegasDisplayMode
Returns one of VegasDisplayMode.SCROLL, FIXED_SEGMENT, or STATIC.
Read from config["vegas_mode"] or override directly.
get_supported_vegas_modes() -> List[VegasDisplayMode]
The set of Vegas modes this plugin can render. Used by the UI to populate the mode selector for this plugin.
get_vegas_segment_width() -> Optional[int]
For FIXED_SEGMENT plugins, the width in pixels of the segment they
occupy in the scroll. None lets the controller pick a default.
The full source for
BasePluginlives insrc/plugin_system/base_plugin.py. If a method here disagrees with the source, the source wins — please open an issue or PR to fix the doc.
Display Manager
The Display Manager handles all rendering operations on the LED matrix. Available as self.display_manager in plugins.
Properties
display_manager.width # Display width in pixels (int)
display_manager.height # Display height in pixels (int)
Core Methods
clear() -> None
Clear the display completely. Creates a new black image.
Note: Does not call update_display() automatically. Call update_display() after drawing new content.
Example:
self.display_manager.clear()
# Draw new content...
self.display_manager.update_display()
update_display() -> None
Update the physical display using double buffering. Call this after drawing all content.
Example:
self.display_manager.draw_text("Hello", x=10, y=10)
self.display_manager.update_display() # Actually show on display
Text Rendering
draw_text(text: str, x: int = None, y: int = None, color: tuple = (255, 255, 255), small_font: bool = False, font: ImageFont = None, centered: bool = False) -> None
Draw text on the canvas.
Parameters:
text(str): Text to displayx(int, optional): X position. IfNone, text is centered horizontally. Ifcentered=True, x is treated as center point.y(int, optional): Y position (default: 0, top of display)color(tuple): RGB color tuple (default: white)small_font(bool): Use small font if Truefont(ImageFont, optional): Custom font object (overrides small_font)centered(bool): If True, x is treated as center point; if False, x is left edge
Example:
# Centered text
self.display_manager.draw_text("Hello", color=(255, 255, 0))
# Left-aligned at specific position
self.display_manager.draw_text("World", x=10, y=20, color=(0, 255, 0))
# Centered at specific x position
self.display_manager.draw_text("Center", x=64, y=16, centered=True)
get_text_width(text: str, font) -> int
Get the width of text when rendered with the given font.
Parameters:
text(str): Text to measurefont: Font object (ImageFont or freetype.Face)
Returns: Width in pixels
Example:
width = self.display_manager.get_text_width("Hello", self.display_manager.regular_font)
x = (self.display_manager.width - width) // 2 # Center text
get_font_height(font) -> int
Get the height of the given font for line spacing purposes.
Parameters:
font: Font object (ImageFont or freetype.Face)
Returns: Height in pixels
Example:
font_height = self.display_manager.get_font_height(self.display_manager.regular_font)
y = 10 + font_height # Position next line
format_date_with_ordinal(dt: datetime) -> str
Format a datetime object into 'Mon Aug 30th' style with ordinal suffix.
Parameters:
dt: datetime object
Returns: Formatted date string
Example:
from datetime import datetime
date_str = self.display_manager.format_date_with_ordinal(datetime.now())
# Returns: "Jan 15th"
Image Rendering
The display manager doesn't provide a dedicated draw_image() method.
Instead, plugins paste directly onto the underlying PIL Image
(display_manager.image), then call update_display() to push the buffer
to the matrix.
from PIL import Image
logo = Image.open("assets/logo.png").convert("RGB")
self.display_manager.image.paste(logo, (10, 10))
self.display_manager.update_display()
For transparency support, paste using a mask:
icon = Image.open("assets/icon.png").convert("RGBA")
self.display_manager.image.paste(icon, (5, 5), icon)
self.display_manager.update_display()
This is the same pattern the bundled scoreboard base classes
(src/base_classes/baseball.py, basketball.py, football.py,
hockey.py) use, so it's the canonical way to render arbitrary images.
Weather Icons
draw_weather_icon(condition: str, x: int, y: int, size: int = 16) -> None
Draw a weather icon based on the condition string.
Parameters:
condition(str): Weather condition (e.g., "clear", "cloudy", "rain", "snow", "storm")x(int): X positiony(int): Y positionsize(int): Icon size in pixels (default: 16)
Supported Conditions:
"clear","sunny"→ Sun icon"clouds","cloudy","partly cloudy"→ Cloud icon"rain","drizzle","shower"→ Rain icon"snow","sleet","hail"→ Snow icon"thunderstorm","storm"→ Storm icon
Example:
self.display_manager.draw_weather_icon("rain", x=10, y=10, size=16)
draw_sun(x: int, y: int, size: int = 16) -> None
Draw a sun icon with rays.
Parameters:
x(int): X positiony(int): Y positionsize(int): Icon size (default: 16)
draw_cloud(x: int, y: int, size: int = 16, color: tuple = (200, 200, 200)) -> None
Draw a cloud icon.
Parameters:
x(int): X positiony(int): Y positionsize(int): Icon size (default: 16)color(tuple): RGB color (default: light gray)
draw_rain(x: int, y: int, size: int = 16) -> None
Draw rain icon with cloud and droplets.
draw_snow(x: int, y: int, size: int = 16) -> None
Draw snow icon with cloud and snowflakes.
draw_text_with_icons(text: str, icons: List[tuple] = None, x: int = None, y: int = None, color: tuple = (255, 255, 255)) -> None
Draw text with weather icons at specified positions.
Parameters:
text(str): Text to displayicons(List[tuple], optional): List of (icon_type, x, y) tuplesx(int, optional): X position for texty(int, optional): Y position for textcolor(tuple): Text color
Note: Automatically calls update_display() after drawing.
Example:
icons = [
("sun", 5, 5),
("cloud", 100, 5)
]
self.display_manager.draw_text_with_icons(
"Weather: Sunny, Cloudy",
icons=icons,
x=10, y=20
)
Scrolling State Management
For plugins that implement scrolling content, use these methods to coordinate with the display system.
set_scrolling_state(is_scrolling: bool) -> None
Mark the display as scrolling or not scrolling. Call when scrolling starts/stops.
Parameters:
is_scrolling(bool): True if currently scrolling, False otherwise
Example:
def display(self, force_clear=False):
self.display_manager.set_scrolling_state(True)
# Scroll content...
self.display_manager.set_scrolling_state(False)
is_currently_scrolling() -> bool
Check if the display is currently in a scrolling state.
Returns: True if scrolling, False otherwise
defer_update(update_func: Callable, priority: int = 0) -> None
Defer an update function to be called when not scrolling. Useful for non-critical updates that should wait until scrolling completes.
Parameters:
update_func: Function to call when not scrollingpriority(int): Priority level (lower numbers = higher priority, default: 0)
Example:
def update(self):
# Critical update - do immediately
self.fetch_data()
# Non-critical update - defer until not scrolling
self.display_manager.defer_update(
lambda: self.update_cache_metadata(),
priority=1
)
process_deferred_updates() -> None
Process any deferred updates if not currently scrolling. Called automatically by the display controller, but can be called manually if needed.
Note: Plugins typically don't need to call this directly.
get_scrolling_stats() -> dict
Get current scrolling statistics for debugging.
Returns: Dictionary with scrolling state information
Example:
stats = self.display_manager.get_scrolling_stats()
self.logger.debug(f"Scrolling: {stats['is_scrolling']}, Deferred: {stats['deferred_count']}")
Available Fonts
The Display Manager provides several pre-loaded fonts:
display_manager.regular_font # Press Start 2P, size 8
display_manager.small_font # Press Start 2P, size 8
display_manager.calendar_font # 5x7 BDF font
display_manager.extra_small_font # 4x6 TTF font, size 6
display_manager.bdf_5x7_font # Alias for calendar_font
Cache Manager
The Cache Manager handles data caching to reduce API calls and improve performance. Available as self.cache_manager in plugins.
Basic Methods
get(key: str, max_age: int = 300) -> Optional[Dict[str, Any]]
Get data from cache if it exists and is not stale.
Parameters:
key(str): Cache keymax_age(int): Maximum age in seconds (default: 300)
Returns: Cached data dictionary, or None if not found or stale
Example:
cached = self.cache_manager.get("weather_data", max_age=600)
if cached:
return cached
set(key: str, data: Dict[str, Any], ttl: Optional[int] = None) -> None
Store data in cache with current timestamp.
Parameters:
key(str): Cache keydata(Dict): Data to cachettl(int, optional): Time-to-live in seconds (for compatibility)
Example:
self.cache_manager.set("weather_data", {
"temp": 72,
"condition": "sunny"
})
clear_cache(key: Optional[str] = None) -> None
Remove a specific cache entry, or all cache entries when called without arguments.
Parameters:
key(str, optional): Cache key to delete. If omitted, every cached entry (memory + disk) is cleared.
Example:
# Drop one stale entry
self.cache_manager.clear_cache("weather_data")
# Nuke everything (rare — typically only used by maintenance tooling)
self.cache_manager.clear_cache()
Advanced Methods
get_cached_data(key: str, max_age: int = 300, memory_ttl: Optional[int] = None) -> Optional[Dict[str, Any]]
Get data from cache with separate memory and disk TTLs.
Parameters:
key(str): Cache keymax_age(int): TTL for persisted (on-disk) entrymemory_ttl(int, optional): TTL for in-memory entry (defaults to max_age)
Returns: Cached data, or None if not found or stale
Example:
# Use memory cache for 60 seconds, disk cache for 1 hour
data = self.cache_manager.get_cached_data(
"api_response",
max_age=3600,
memory_ttl=60
)
get_cached_data_with_strategy(key: str, data_type: str = 'default') -> Optional[Dict[str, Any]]
Get data using data-type-specific cache strategy. Automatically selects appropriate TTL based on data type.
Parameters:
key(str): Cache keydata_type(str): Data type for strategy selection (e.g., 'weather', 'sports_live', 'stocks')
Returns: Cached data, or None if not found or stale
Example:
# Automatically uses appropriate cache duration for weather data
weather = self.cache_manager.get_cached_data_with_strategy(
"weather_current",
data_type="weather"
)
get_with_auto_strategy(key: str) -> Optional[Dict[str, Any]]
Get data with automatic strategy detection from cache key.
Parameters:
key(str): Cache key (strategy inferred from key name)
Returns: Cached data, or None if not found or stale
Example:
# Strategy automatically detected from key name
data = self.cache_manager.get_with_auto_strategy("nhl_live_scores")
get_background_cached_data(key: str, sport_key: Optional[str] = None) -> Optional[Dict[str, Any]]
Get background service cached data with sport-specific intervals.
Parameters:
key(str): Cache keysport_key(str, optional): Sport identifier (e.g., 'nhl', 'nba') for live interval lookup
Returns: Cached data, or None if not found or stale
Example:
# Uses sport-specific live_update_interval from config
games = self.cache_manager.get_background_cached_data(
"nhl_games",
sport_key="nhl"
)
Strategy Methods
get_cache_strategy(data_type: str, sport_key: Optional[str] = None) -> Dict[str, Any]
Get cache strategy configuration for a data type.
Parameters:
data_type(str): Data type (e.g., 'weather', 'sports_live', 'stocks')sport_key(str, optional): Sport identifier for sport-specific strategies
Returns: Dictionary with strategy configuration (max_age, memory_ttl, etc.)
Example:
strategy = self.cache_manager.get_cache_strategy("sports_live", sport_key="nhl")
max_age = strategy['max_age'] # Get configured max age
get_sport_live_interval(sport_key: str) -> int
Get the live_update_interval for a specific sport from config.
Parameters:
sport_key(str): Sport identifier (e.g., 'nhl', 'nba')
Returns: Live update interval in seconds
Example:
interval = self.cache_manager.get_sport_live_interval("nhl")
# Returns configured live_update_interval for NHL
get_data_type_from_key(key: str) -> str
Extract data type from cache key to determine appropriate cache strategy.
Parameters:
key(str): Cache key
Returns: Inferred data type string
get_sport_key_from_cache_key(key: str) -> Optional[str]
Extract sport key from cache key for sport-specific strategies.
Parameters:
key(str): Cache key
Returns: Sport identifier, or None if not found
Utility Methods
clear_cache(key: Optional[str] = None) -> None
Clear cache for a specific key or all keys.
Parameters:
key(str, optional): Specific key to clear. IfNone, clears all cache.
Example:
# Clear specific key
self.cache_manager.clear_cache("weather_data")
# Clear all cache
self.cache_manager.clear_cache()
get_cache_dir() -> Optional[str]
Get the cache directory path.
Returns: Cache directory path string, or None if not available
list_cache_files() -> List[Dict[str, Any]]
List all cache files with metadata.
Returns: List of dictionaries with cache file information (key, age, size, path, etc.)
Example:
files = self.cache_manager.list_cache_files()
for file_info in files:
self.logger.info(f"Cache: {file_info['key']}, Age: {file_info['age_display']}")
Metrics Methods
get_cache_metrics() -> Dict[str, Any]
Get cache performance metrics.
Returns: Dictionary with cache statistics (hits, misses, hit rate, etc.)
Example:
metrics = self.cache_manager.get_cache_metrics()
self.logger.info(f"Cache hit rate: {metrics['hit_rate']:.2%}")
get_memory_cache_stats() -> Dict[str, Any]
Get memory cache statistics.
Returns: Dictionary with memory cache stats (size, max_size, etc.)
Plugin Manager
The Plugin Manager provides access to other plugins and plugin system information. Available as self.plugin_manager in plugins.
Methods
get_plugin(plugin_id: str) -> Optional[Any]
Get a plugin instance by ID.
Parameters:
plugin_id(str): Plugin identifier
Returns: Plugin instance, or None if not found
Example:
weather_plugin = self.plugin_manager.get_plugin("weather")
if weather_plugin:
# Access weather plugin data
pass
get_all_plugins() -> Dict[str, Any]
Get all loaded plugin instances.
Returns: Dictionary mapping plugin_id to plugin instance
Example:
all_plugins = self.plugin_manager.get_all_plugins()
for plugin_id, plugin in all_plugins.items():
self.logger.info(f"Plugin {plugin_id} is loaded")
get_enabled_plugins() -> List[str]
Get list of enabled plugin IDs.
Returns: List of plugin identifier strings
get_plugin_info(plugin_id: str) -> Optional[Dict[str, Any]]
Get plugin information including manifest and runtime info.
Parameters:
plugin_id(str): Plugin identifier
Returns: Dictionary with plugin information, or None if not found
Example:
info = self.plugin_manager.get_plugin_info("weather")
if info:
self.logger.info(f"Plugin: {info['name']}, Version: {info.get('version')}")
get_all_plugin_info() -> List[Dict[str, Any]]
Get information for all plugins.
Returns: List of plugin information dictionaries
get_plugin_directory(plugin_id: str) -> Optional[str]
Get the directory path for a plugin.
Parameters:
plugin_id(str): Plugin identifier
Returns: Directory path string, or None if not found
get_plugin_display_modes(plugin_id: str) -> List[str]
Get list of display modes for a plugin.
Parameters:
plugin_id(str): Plugin identifier
Returns: List of display mode names
Example:
modes = self.plugin_manager.get_plugin_display_modes("football-scoreboard")
# Returns: ['nfl_live', 'nfl_recent', 'nfl_upcoming', ...]
Plugin Manifests
Access plugin manifests through self.plugin_manager.plugin_manifests:
# Get manifest for a plugin
manifest = self.plugin_manager.plugin_manifests.get(self.plugin_id, {})
# Access manifest fields
display_modes = manifest.get('display_modes', [])
version = manifest.get('version')
Inter-Plugin Communication
Plugins can communicate with each other through the Plugin Manager:
Example - Getting data from another plugin:
def update(self):
# Get weather plugin
weather_plugin = self.plugin_manager.get_plugin("weather")
if weather_plugin and hasattr(weather_plugin, 'current_temp'):
self.temp = weather_plugin.current_temp
Example - Checking if another plugin is enabled:
enabled_plugins = self.plugin_manager.get_enabled_plugins()
if "weather" in enabled_plugins:
# Weather plugin is enabled
pass
Best Practices
Caching
-
Use appropriate cache keys: Include plugin ID and data type in keys
cache_key = f"{self.plugin_id}_weather_current" -
Use cache strategies: Prefer
get_cached_data_with_strategy()for automatic TTL selectiondata = self.cache_manager.get_cached_data_with_strategy( f"{self.plugin_id}_data", data_type="weather" ) -
Handle cache misses: Always check for
Nonereturn valuescached = self.cache_manager.get(key, max_age=3600) if not cached: cached = self._fetch_from_api() self.cache_manager.set(key, cached)
Display Rendering
-
Always call update_display(): After drawing content, call
update_display()self.display_manager.draw_text("Hello", x=10, y=10) self.display_manager.update_display() # Required! -
Use clear() appropriately: Only clear when necessary (e.g.,
force_clear=True)def display(self, force_clear=False): if force_clear: self.display_manager.clear() # Draw content... self.display_manager.update_display() -
Handle scrolling state: If your plugin scrolls, use scrolling state methods
self.display_manager.set_scrolling_state(True) # Scroll content... self.display_manager.set_scrolling_state(False)
Error Handling
-
Log errors appropriately: Use
self.loggerfor plugin-specific loggingtry: data = self._fetch_data() except Exception as e: self.logger.error(f"Failed to fetch data: {e}") return -
Handle missing data gracefully: Provide fallback displays when data is unavailable
if not self.data: self.display_manager.draw_text("No data available", x=10, y=16) self.display_manager.update_display() return
See Also
- BasePlugin Source - Base plugin implementation
- Display Manager Source - Display manager implementation
- Cache Manager Source - Cache manager implementation
- Plugin Manager Source - Plugin manager implementation
- Plugin Development Guide - Complete development guide
- Advanced Plugin Development - Advanced patterns and examples