Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
db31864
Opening Bell
ChuckBuilds Apr 9, 2025
04cfa86
Update stock_manager.py
ChuckBuilds Apr 9, 2025
e87251c
Update stock_manager.py
ChuckBuilds Apr 9, 2025
cb8680e
Update stock_manager.py
ChuckBuilds Apr 9, 2025
bac08af
Update stock_manager.py
ChuckBuilds Apr 9, 2025
730450f
Update stock_manager.py
ChuckBuilds Apr 9, 2025
31e57ad
Update display_controller.py
ChuckBuilds Apr 9, 2025
f527f4b
Update stock_manager.py
ChuckBuilds Apr 9, 2025
a7982ba
Update stock_manager.py
ChuckBuilds Apr 9, 2025
9ea3169
Customize Display timings
ChuckBuilds Apr 9, 2025
75ff427
Update stock_manager.py
ChuckBuilds Apr 9, 2025
f6be7b5
Sizing and Spacing
ChuckBuilds Apr 9, 2025
8e98a6d
Update clock.py
ChuckBuilds Apr 9, 2025
2871766
Update stock_manager.py
ChuckBuilds Apr 9, 2025
db96c29
Update stock_manager.py
ChuckBuilds Apr 9, 2025
69ff4b3
readme update
ChuckBuilds Apr 11, 2025
21cd5c9
Update .gitignore
ChuckBuilds Apr 11, 2025
1610afd
Update config.json
ChuckBuilds Apr 11, 2025
a01c902
Stock News
ChuckBuilds Apr 11, 2025
5034913
Update config.json
ChuckBuilds Apr 11, 2025
e24c46b
Scroll Performance
ChuckBuilds Apr 11, 2025
99d9990
updating scroll direction
ChuckBuilds Apr 11, 2025
8f11ae3
News tuning
ChuckBuilds Apr 11, 2025
8a71971
Create test_news_manager.py
ChuckBuilds Apr 11, 2025
55db83d
Update test_news_manager.py
ChuckBuilds Apr 11, 2025
83d5726
troubleshooting test script
ChuckBuilds Apr 11, 2025
da4615e
Update test_news_manager.py
ChuckBuilds Apr 11, 2025
2128a2f
Update config.json
ChuckBuilds Apr 11, 2025
fd9006c
Update config.json
ChuckBuilds Apr 11, 2025
56594f9
Update config.json
ChuckBuilds Apr 11, 2025
8ec1c70
Update config.json
ChuckBuilds Apr 11, 2025
53689dc
Update config.json
ChuckBuilds Apr 11, 2025
be3c5da
Update config.json
ChuckBuilds Apr 11, 2025
80ac45c
Update test_news_manager.py
ChuckBuilds Apr 11, 2025
f3fd77c
scroll tuning
ChuckBuilds Apr 11, 2025
2a127b1
scroll logging and debugging
ChuckBuilds Apr 11, 2025
e14f7dd
Update config.json
ChuckBuilds Apr 11, 2025
ba65a2c
Update news_manager.py
ChuckBuilds Apr 11, 2025
6091c71
Update news_manager.py
ChuckBuilds Apr 11, 2025
beeeda5
Stock News manager Rename
ChuckBuilds Apr 11, 2025
013b5e2
Update display_controller.py
ChuckBuilds Apr 11, 2025
1d2bef0
Update stock_manager.py
ChuckBuilds Apr 11, 2025
5be0d59
Stock news settings
ChuckBuilds Apr 11, 2025
7925bf5
Stock news joins the lineup
ChuckBuilds Apr 11, 2025
e8aa05a
Optimize scrolling text performance for news ticker
ChuckBuilds Apr 11, 2025
7461a3e
Adjust matrix settings to reduce artifacting while maintaining perfor…
ChuckBuilds Apr 11, 2025
702c6d2
changed float to integer
ChuckBuilds Apr 11, 2025
45d9f1e
Fix news ticker performance with simplified scrolling mechanism
ChuckBuilds Apr 11, 2025
ff34400
Fix stock news scrolling in test environment: - Optimize display mana…
ChuckBuilds Apr 11, 2025
73f836a
Merge commit 'ff34400' into Stocks
ChuckBuilds Apr 11, 2025
a7a341b
Optimize stock news scrolling for better performance: - Use pre-rende…
ChuckBuilds Apr 11, 2025
f3975e1
Optimize stock news display performance: - Cache text image to reduce…
ChuckBuilds Apr 11, 2025
1f867e6
Optimize stock news display in controller: - Remove global sleep dela…
ChuckBuilds Apr 11, 2025
fbaca69
Merge branch 'main' into Stocks
ChuckBuilds Apr 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"enabled": true,
"update_interval": 300,
"scroll_speed": 1,
"scroll_delay": 0.0001,
"scroll_delay": 0.001,
"max_headlines_per_symbol": 1,
"headlines_per_rotation": 2
}
Expand Down
9 changes: 5 additions & 4 deletions src/display_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,22 +113,26 @@ def run(self):
logger.info(f"Switching display to: {self.current_display} {self.weather_mode if self.current_display == 'weather' else ''}")
self.last_switch = current_time
self.force_clear = True
self.display_manager.clear() # Ensure clean transition
self.display_manager.clear()

# Display current screen
try:
if self.current_display == 'clock' and self.config.get('clock', {}).get('enabled', False):
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):
if self.weather_mode == 'current':
self.weather.display_weather(force_clear=self.force_clear)
elif self.weather_mode == 'hourly':
self.weather.display_hourly_forecast(force_clear=self.force_clear)
else: # daily
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):
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):
# For news, we want to update as fast as possible without delay
self.news.display_news()
except Exception as e:
logger.error(f"Error updating display: {e}")
Expand All @@ -138,9 +142,6 @@ def run(self):
# Reset force clear flag after use
self.force_clear = False

# Sleep between updates
time.sleep(self.update_interval)

except KeyboardInterrupt:
print("\nDisplay stopped by user")
finally:
Expand Down
19 changes: 8 additions & 11 deletions src/display_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,18 @@ def _setup_matrix(self):
options.parallel = hardware_config.get('parallel', 1)
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.pwm_bits = 11
options.pwm_lsb_nanoseconds = 200 # Increased for better stability
options.pwm_bits = 10 # Increased from 8 for better color depth
options.pwm_lsb_nanoseconds = 150 # Increased for better stability
options.led_rgb_sequence = 'RGB'
options.pixel_mapper_config = ''
options.row_address_type = 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.limit_refresh_rate_hz = 60 # Reduced refresh rate for stability
options.gpio_slowdown = 2 # Increased slowdown for better stability
options.limit_refresh_rate_hz = 90 # Reduced from 120Hz for better stability
options.gpio_slowdown = 2 # Increased for better stability

# Initialize the matrix
self.matrix = RGBMatrix(options=options)
Expand Down Expand Up @@ -94,14 +94,11 @@ def update_display(self):
# Copy the current image to the offscreen canvas
self.offscreen_canvas.SetImage(self.image)

# Wait for the next vsync before swapping
self.matrix.SwapOnVSync(self.offscreen_canvas)
# Swap buffers immediately
self.matrix.SwapOnVSync(self.offscreen_canvas, False)

# Swap our canvas references
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:
logger.error(f"Error updating display: {e}")

Expand Down
68 changes: 46 additions & 22 deletions src/stock_news_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ def __init__(self, config: Dict[str, Any], display_manager):
self.news_data = {}
self.current_news_group = 0 # Track which group of headlines we're showing
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
self.scroll_speed = self.stock_news_config.get('scroll_speed', 1)
Expand Down Expand Up @@ -205,39 +208,60 @@ def display_news(self):
separator = " - " # Visual separator between news items
news_text = separator.join(news_texts)

# Clear the display
self.display_manager.clear()

# Calculate text width for scrolling
bbox = self.display_manager.draw.textbbox((0, 0), news_text, font=self.display_manager.small_font)
text_width = bbox[2] - bbox[0]
# Only create new text image if the text has changed
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 scroll position
if not self.cached_text_image:
return

text_width = self.cached_text_image.width
text_height = self.cached_text_image.height

display_width = self.display_manager.matrix.width
total_width = text_width + display_width

# Update scroll position
self.scroll_position = (self.scroll_position + self.scroll_speed) % total_width

# Draw the text at the current scroll position
self.display_manager.draw_text(
news_text,
x=display_width - self.scroll_position,
y=None, # Center vertically
color=(255, 255, 255),
small_font=True
)

# Update the display
self.display_manager.update_display()

# Calculate the visible portion of the text
visible_width = min(display_width, text_width - self.scroll_position)
if visible_width > 0:
# Create a new blank image for this frame
frame_image = Image.new('RGB', (display_width, text_height), (0, 0, 0))

# Crop and paste in one operation
if self.scroll_position + visible_width <= text_width:
# Normal case - text is still scrolling in
visible_portion = self.cached_text_image.crop((
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 self.scroll_position == 0:
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
self._log_frame_rate()

Expand Down
16 changes: 16 additions & 0 deletions test_stock_news_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

def main():
"""Test the StockNewsManager class directly."""

display_manager = None

try:
# Load configuration
config_manager = ConfigManager()
Expand All @@ -27,6 +30,13 @@ def main():
# Initialize display manager
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
news_manager = StockNewsManager(config, display_manager)

Expand All @@ -43,6 +53,12 @@ def main():
import traceback
traceback.print_exc()
finally:
if display_manager:
# Clear the display before exiting
display_manager.clear()
display_manager.update_display()
display_manager.cleanup()

print("Test completed")

if __name__ == "__main__":
Expand Down