mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +00:00
Stocks (#2)
* Opening Bell Introducing the Stock Ticker Feature * Update stock_manager.py Assume folder exists * Update stock_manager.py removing logos to focus on function for now * Update stock_manager.py parse yahoo scripts * Update stock_manager.py stock query update * Update stock_manager.py slow down stock display * Update display_controller.py adjust screen flow * Update stock_manager.py shipping features * Update stock_manager.py stock refresh in the background * Customize Display timings customize display timings * Update stock_manager.py stock font size change * Sizing and Spacing CHanged font sizing on chart and clock spacing * Update clock.py Date format changes * Update stock_manager.py actually read stocks from config file * Update stock_manager.py add config manager * readme update readme update and formatting for better flow * Update .gitignore rename reference folder * Update config.json changed default stocks to test update implementation * Stock News Stock news Ticker * Update config.json increase scroll speed * Scroll Performance Tuning news scrolling performance * updating scroll direction orienting scroll direction * News tuning removed test files and increased scroll speed * Create test_news_manager.py need a test script to call upon * Update test_news_manager.py test script tuning * troubleshooting test script * Update test_news_manager.py * Update config.json scroll speed increases * Update config.json scroll tuning * Update config.json speeding up * Update config.json still making text faster * Update config.json Trying to tune scrolling * Update config.json testing crazy parameters * Update test_news_manager.py remove sleep delay * scroll tuning scroll tuning * scroll logging and debugging FPS counter and debug messages * Update config.json matrix speed tuning * Update news_manager.py News separator * Update news_manager.py separator character change * Stock News manager Rename rename stock news ticker to enable other news in the future * Update display_controller.py load config update * Update stock_manager.py remove redundant import * Stock news settings Stock news has more granular control * Stock news joins the lineup Stock News added to the display controller and drawing display instead of image * Optimize scrolling text performance for news ticker * Adjust matrix settings to reduce artifacting while maintaining performance * changed float to integer * Fix news ticker performance with simplified scrolling mechanism * Fix stock news scrolling in test environment: - Optimize display manager settings for smooth scrolling - Add proper display initialization and cleanup in test script - Implement timing control to prevent display buffer overflow - Ensure consistent 1ms delay between updates for smooth scrolling * Optimize stock news scrolling for better performance: - Use pre-rendered text image for efficient scrolling - Implement cropping and pasting for smoother animation - Remove unnecessary display operations and delays * Optimize stock news display performance: - Cache text image to reduce rendering overhead - Improve frame creation and update logic - Optimize text wrapping for smoother scrolling - Remove unnecessary display clears * Optimize stock news display in controller: - Remove global sleep delay - Allow news display to run at full speed - Keep slower update rates for other displays --------- Signed-off-by: Chuck <33324927+ChuckBuilds@users.noreply.github.com>
This commit is contained in:
@@ -57,7 +57,7 @@
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"update_interval": 300,
|
"update_interval": 300,
|
||||||
"scroll_speed": 1,
|
"scroll_speed": 1,
|
||||||
"scroll_delay": 0.0001,
|
"scroll_delay": 0.001,
|
||||||
"max_headlines_per_symbol": 1,
|
"max_headlines_per_symbol": 1,
|
||||||
"headlines_per_rotation": 2
|
"headlines_per_rotation": 2
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,12 +113,13 @@ class DisplayController:
|
|||||||
logger.info(f"Switching display to: {self.current_display} {self.weather_mode if self.current_display == 'weather' else ''}")
|
logger.info(f"Switching display to: {self.current_display} {self.weather_mode if self.current_display == 'weather' else ''}")
|
||||||
self.last_switch = current_time
|
self.last_switch = current_time
|
||||||
self.force_clear = True
|
self.force_clear = True
|
||||||
self.display_manager.clear() # Ensure clean transition
|
self.display_manager.clear()
|
||||||
|
|
||||||
# Display current screen
|
# Display current screen
|
||||||
try:
|
try:
|
||||||
if self.current_display == 'clock' and self.config.get('clock', {}).get('enabled', False):
|
if self.current_display == 'clock' and self.config.get('clock', {}).get('enabled', False):
|
||||||
self.clock.display_time(force_clear=self.force_clear)
|
self.clock.display_time(force_clear=self.force_clear)
|
||||||
|
time.sleep(self.update_interval)
|
||||||
elif self.current_display == 'weather' and self.config.get('weather', {}).get('enabled', False):
|
elif self.current_display == 'weather' and self.config.get('weather', {}).get('enabled', False):
|
||||||
if self.weather_mode == 'current':
|
if self.weather_mode == 'current':
|
||||||
self.weather.display_weather(force_clear=self.force_clear)
|
self.weather.display_weather(force_clear=self.force_clear)
|
||||||
@@ -126,9 +127,12 @@ class DisplayController:
|
|||||||
self.weather.display_hourly_forecast(force_clear=self.force_clear)
|
self.weather.display_hourly_forecast(force_clear=self.force_clear)
|
||||||
else: # daily
|
else: # daily
|
||||||
self.weather.display_daily_forecast(force_clear=self.force_clear)
|
self.weather.display_daily_forecast(force_clear=self.force_clear)
|
||||||
|
time.sleep(self.update_interval)
|
||||||
elif self.current_display == 'stocks' and self.config.get('stocks', {}).get('enabled', False):
|
elif self.current_display == 'stocks' and self.config.get('stocks', {}).get('enabled', False):
|
||||||
self.stocks.display_stocks(force_clear=self.force_clear)
|
self.stocks.display_stocks(force_clear=self.force_clear)
|
||||||
|
time.sleep(self.update_interval)
|
||||||
elif self.current_display == 'stock_news' and self.config.get('stock_news', {}).get('enabled', False):
|
elif self.current_display == 'stock_news' and self.config.get('stock_news', {}).get('enabled', False):
|
||||||
|
# For news, we want to update as fast as possible without delay
|
||||||
self.news.display_news()
|
self.news.display_news()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error updating display: {e}")
|
logger.error(f"Error updating display: {e}")
|
||||||
@@ -138,9 +142,6 @@ class DisplayController:
|
|||||||
# Reset force clear flag after use
|
# Reset force clear flag after use
|
||||||
self.force_clear = False
|
self.force_clear = False
|
||||||
|
|
||||||
# Sleep between updates
|
|
||||||
time.sleep(self.update_interval)
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nDisplay stopped by user")
|
print("\nDisplay stopped by user")
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -36,18 +36,18 @@ class DisplayManager:
|
|||||||
options.parallel = hardware_config.get('parallel', 1)
|
options.parallel = hardware_config.get('parallel', 1)
|
||||||
options.hardware_mapping = hardware_config.get('hardware_mapping', 'adafruit-hat-pwm')
|
options.hardware_mapping = hardware_config.get('hardware_mapping', 'adafruit-hat-pwm')
|
||||||
|
|
||||||
# Optimize display settings for chained panels
|
# Balance performance and stability
|
||||||
options.brightness = 100
|
options.brightness = 100
|
||||||
options.pwm_bits = 11
|
options.pwm_bits = 10 # Increased from 8 for better color depth
|
||||||
options.pwm_lsb_nanoseconds = 200 # Increased for better stability
|
options.pwm_lsb_nanoseconds = 150 # Increased for better stability
|
||||||
options.led_rgb_sequence = 'RGB'
|
options.led_rgb_sequence = 'RGB'
|
||||||
options.pixel_mapper_config = ''
|
options.pixel_mapper_config = ''
|
||||||
options.row_address_type = 0
|
options.row_address_type = 0
|
||||||
options.multiplexing = 0
|
options.multiplexing = 0
|
||||||
options.disable_hardware_pulsing = False # Enable hardware pulsing for better sync
|
options.disable_hardware_pulsing = False # Re-enable hardware pulsing for stability
|
||||||
options.show_refresh_rate = False
|
options.show_refresh_rate = False
|
||||||
options.limit_refresh_rate_hz = 60 # Reduced refresh rate for stability
|
options.limit_refresh_rate_hz = 90 # Reduced from 120Hz for better stability
|
||||||
options.gpio_slowdown = 2 # Increased slowdown for better stability
|
options.gpio_slowdown = 2 # Increased for better stability
|
||||||
|
|
||||||
# Initialize the matrix
|
# Initialize the matrix
|
||||||
self.matrix = RGBMatrix(options=options)
|
self.matrix = RGBMatrix(options=options)
|
||||||
@@ -94,14 +94,11 @@ class DisplayManager:
|
|||||||
# Copy the current image to the offscreen canvas
|
# Copy the current image to the offscreen canvas
|
||||||
self.offscreen_canvas.SetImage(self.image)
|
self.offscreen_canvas.SetImage(self.image)
|
||||||
|
|
||||||
# Wait for the next vsync before swapping
|
# Swap buffers immediately
|
||||||
self.matrix.SwapOnVSync(self.offscreen_canvas)
|
self.matrix.SwapOnVSync(self.offscreen_canvas, False)
|
||||||
|
|
||||||
# Swap our canvas references
|
# Swap our canvas references
|
||||||
self.offscreen_canvas, self.current_canvas = self.current_canvas, self.offscreen_canvas
|
self.offscreen_canvas, self.current_canvas = self.current_canvas, self.offscreen_canvas
|
||||||
|
|
||||||
# Small delay to ensure stable refresh
|
|
||||||
time.sleep(0.001)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error updating display: {e}")
|
logger.error(f"Error updating display: {e}")
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ class StockNewsManager:
|
|||||||
self.news_data = {}
|
self.news_data = {}
|
||||||
self.current_news_group = 0 # Track which group of headlines we're showing
|
self.current_news_group = 0 # Track which group of headlines we're showing
|
||||||
self.scroll_position = 0
|
self.scroll_position = 0
|
||||||
|
self.cached_text_image = None # Cache for the text image
|
||||||
|
self.cached_text = None # Cache for the text string
|
||||||
|
|
||||||
|
|
||||||
# Get scroll settings from config with faster defaults
|
# Get scroll settings from config with faster defaults
|
||||||
self.scroll_speed = self.stock_news_config.get('scroll_speed', 1)
|
self.scroll_speed = self.stock_news_config.get('scroll_speed', 1)
|
||||||
@@ -205,39 +208,60 @@ class StockNewsManager:
|
|||||||
separator = " - " # Visual separator between news items
|
separator = " - " # Visual separator between news items
|
||||||
news_text = separator.join(news_texts)
|
news_text = separator.join(news_texts)
|
||||||
|
|
||||||
# Clear the display
|
# Only create new text image if the text has changed
|
||||||
self.display_manager.clear()
|
if news_text != self.cached_text:
|
||||||
|
self.cached_text = news_text
|
||||||
|
self.cached_text_image = self._create_text_image(news_text)
|
||||||
|
self.scroll_position = 0 # Reset scroll position for new text
|
||||||
|
|
||||||
# Calculate text width for scrolling
|
if not self.cached_text_image:
|
||||||
bbox = self.display_manager.draw.textbbox((0, 0), news_text, font=self.display_manager.small_font)
|
return
|
||||||
text_width = bbox[2] - bbox[0]
|
|
||||||
|
text_width = self.cached_text_image.width
|
||||||
# Calculate scroll position
|
text_height = self.cached_text_image.height
|
||||||
|
|
||||||
display_width = self.display_manager.matrix.width
|
display_width = self.display_manager.matrix.width
|
||||||
total_width = text_width + display_width
|
total_width = text_width + display_width
|
||||||
|
|
||||||
# Update scroll position
|
# Update scroll position
|
||||||
self.scroll_position = (self.scroll_position + self.scroll_speed) % total_width
|
self.scroll_position = (self.scroll_position + self.scroll_speed) % total_width
|
||||||
|
|
||||||
# Draw the text at the current scroll position
|
# Calculate the visible portion of the text
|
||||||
self.display_manager.draw_text(
|
visible_width = min(display_width, text_width - self.scroll_position)
|
||||||
news_text,
|
if visible_width > 0:
|
||||||
x=display_width - self.scroll_position,
|
# Create a new blank image for this frame
|
||||||
y=None, # Center vertically
|
frame_image = Image.new('RGB', (display_width, text_height), (0, 0, 0))
|
||||||
color=(255, 255, 255),
|
|
||||||
small_font=True
|
# Crop and paste in one operation
|
||||||
)
|
if self.scroll_position + visible_width <= text_width:
|
||||||
|
# Normal case - text is still scrolling in
|
||||||
# Update the display
|
visible_portion = self.cached_text_image.crop((
|
||||||
self.display_manager.update_display()
|
self.scroll_position, 0,
|
||||||
|
self.scroll_position + visible_width, text_height
|
||||||
|
))
|
||||||
|
frame_image.paste(visible_portion, (0, 0))
|
||||||
|
else:
|
||||||
|
# Wrapping case - text is wrapping around
|
||||||
|
first_part_width = text_width - self.scroll_position
|
||||||
|
first_part = self.cached_text_image.crop((
|
||||||
|
self.scroll_position, 0,
|
||||||
|
text_width, text_height
|
||||||
|
))
|
||||||
|
second_part = self.cached_text_image.crop((
|
||||||
|
0, 0,
|
||||||
|
visible_width - first_part_width, text_height
|
||||||
|
))
|
||||||
|
frame_image.paste(first_part, (0, 0))
|
||||||
|
frame_image.paste(second_part, (first_part_width, 0))
|
||||||
|
|
||||||
|
# Update the display with the new frame
|
||||||
|
self.display_manager.image = frame_image
|
||||||
|
self.display_manager.update_display()
|
||||||
|
|
||||||
# If we've completed a full scroll, move to the next group
|
# If we've completed a full scroll, move to the next group
|
||||||
if self.scroll_position == 0:
|
if self.scroll_position == 0:
|
||||||
self.current_news_group = (self.current_news_group + 1) % ((total_headlines + headlines_per_rotation - 1) // headlines_per_rotation)
|
self.current_news_group = (self.current_news_group + 1) % ((total_headlines + headlines_per_rotation - 1) // headlines_per_rotation)
|
||||||
|
|
||||||
# Small delay to control scroll speed
|
|
||||||
time.sleep(self.scroll_delay)
|
|
||||||
|
|
||||||
# Log frame rate
|
# Log frame rate
|
||||||
self._log_frame_rate()
|
self._log_frame_rate()
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ print(f"Current working directory: {os.getcwd()}")
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Test the StockNewsManager class directly."""
|
"""Test the StockNewsManager class directly."""
|
||||||
|
|
||||||
|
display_manager = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Load configuration
|
# Load configuration
|
||||||
config_manager = ConfigManager()
|
config_manager = ConfigManager()
|
||||||
@@ -27,6 +30,13 @@ def main():
|
|||||||
# Initialize display manager
|
# Initialize display manager
|
||||||
display_manager = DisplayManager(display_config)
|
display_manager = DisplayManager(display_config)
|
||||||
|
|
||||||
|
|
||||||
|
# Clear the display and show a test pattern
|
||||||
|
display_manager.clear()
|
||||||
|
display_manager.update_display()
|
||||||
|
time.sleep(1) # Give time to see the test pattern
|
||||||
|
|
||||||
|
|
||||||
# Initialize news manager with the loaded config
|
# Initialize news manager with the loaded config
|
||||||
news_manager = StockNewsManager(config, display_manager)
|
news_manager = StockNewsManager(config, display_manager)
|
||||||
|
|
||||||
@@ -43,6 +53,12 @@ def main():
|
|||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
finally:
|
finally:
|
||||||
|
if display_manager:
|
||||||
|
# Clear the display before exiting
|
||||||
|
display_manager.clear()
|
||||||
|
display_manager.update_display()
|
||||||
|
display_manager.cleanup()
|
||||||
|
|
||||||
print("Test completed")
|
print("Test completed")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user