1- # SPDX-FileCopyrightText: 2025 Pedro for Adafruit Industries
1+ # SPDX-FileCopyrightText: 2025 Pedro Ruiz for Adafruit Industries
22#
33# SPDX-License-Identifier: MIT
44
55'''PyPortal Art Display - Multi-feed with mode switching'''
66
7- # pylint: disable=global-statement,invalid-name,too-many-lines
8- # pylint: disable=redefined-outer-name
9- # pylint: disable=no-member,protected-access,too-many-return-statements
7+ # pylint: disable=too-many-lines
108
119from os import getenv
1210import os
9189# Skip button on D3 (pull-up, active low)
9290# Set to False if no button is wired to D3
9391USE_BUTTON = False
94- skip_btn = None
92+ skip_btn = None # pylint: disable=invalid-name
9593if USE_BUTTON :
9694 skip_btn = digitalio .DigitalInOut (board .D3 )
9795 skip_btn .direction = digitalio .Direction .INPUT
10098# --- LIS3DH Accelerometer (STEMMA I2C) ---
10199# Set to False if no accelerometer is connected
102100USE_ACCEL = True
103- lis3dh = None
101+ lis3dh = None # pylint: disable=invalid-name
104102
105103# Tilt detection: LIS3DH on back of diamond-oriented display.
106104# Resting position reads ~-10 to -16 degrees on Z-axis.
110108TILT_THRESHOLD_LEFT = 6 # +/- tolerance (range: 0 to -3)
111109TILT_THRESHOLD_RIGHT = 30 # +/- tolerance (range: -50 to -70)
112110TILT_HOLD_TIME = 0.5 # seconds to hold at target before skip
113- tilt_start = 0.0 # when tilt was first detected
111+ tilt_start = 0.0 # pylint: disable=invalid-name
114112
115113# Shake detection: triggers skip on quick shake gesture
116114SHAKE_THRESHOLD = 20.0 # m/s^2 total accel (1g = 9.8)
126124 print ("LIS3DH accelerometer found!" )
127125 except (ValueError , RuntimeError ) as accel_err :
128126 print ("No LIS3DH found:" , accel_err )
129- lis3dh = None
127+ lis3dh = None # pylint: disable=invalid-name
130128 USE_ACCEL = False
131129
132130# --- Audio confirmation chirp ---
@@ -140,8 +138,8 @@ def play_beep():
140138 '''Play confirmation chirp via PyPortal's built-in audio.'''
141139 try :
142140 pyportal .play_file (BEEP_WAV )
143- except (OSError , RuntimeError ) as err :
144- print ("Beep error:" , err )
141+ except (OSError , RuntimeError ) as beep_err :
142+ print ("Beep error:" , beep_err )
145143
146144
147145def get_tilt_angle ():
@@ -248,15 +246,15 @@ def tap_aware_sleep(seconds):
248246def count_taps ():
249247 '''Count total taps within TAP_WINDOW after first tap.
250248 Returns tap count (1 if no extra taps detected).'''
251- taps = 1
249+ tap_count = 1
252250 deadline = time .monotonic () + TAP_WINDOW
253251 while time .monotonic () < deadline :
254252 if check_tap ():
255- taps = taps + 1
253+ tap_count = tap_count + 1
256254 # Reset window on each tap
257255 deadline = time .monotonic () + TAP_WINDOW
258256 time .sleep (0.1 )
259- return taps
257+ return tap_count
260258
261259
262260# ============================================
@@ -308,8 +306,8 @@ def wipe_transition(bg_path):
308306
309307 # Animate with ease-in (accelerates like dripping)
310308 for step in range (1 , WIPE_STEPS + 1 ):
311- t = step / WIPE_STEPS
312- progress = t * t # ease-in: slow start, fast finish
309+ frac = step / WIPE_STEPS
310+ progress = frac * frac # ease-in: slow start, fast finish
313311 new_x = int (- WIDTH * WIPE_X_BIAS * (1.0 - progress ))
314312 new_y = int (- HEIGHT * (1.0 - progress ))
315313 new_tile .x = new_x
@@ -376,7 +374,7 @@ def url_encode(url_string):
376374if not AIO_KEY :
377375 missing_keys .append ("AIO_KEY" )
378376 print ("WARNING: ADAFRUIT_AIO_KEY missing" )
379- online_available = len (missing_keys ) == 0
377+ online_available = len (missing_keys ) == 0 # pylint: disable=invalid-name
380378
381379BACKGROUND_FILE = "/background.bmp"
382380if WIDTH > 320 :
@@ -389,10 +387,9 @@ def url_encode(url_string):
389387 image_position = (0 , 0 ),
390388)
391389
392- # Generate chirp WAV file on SD card at startup
393- try :
394- import struct
395- # 16-bit mono PCM WAV at 16000 Hz
390+ def generate_chirp_wav ():
391+ '''Generate chirp WAV file on SD card.'''
392+ import struct # pylint: disable=import-outside-toplevel
396393 sample_rate = 16000
397394 num_samples = int (sample_rate * BEEP_DURATION )
398395 data_size = num_samples * 2
@@ -410,14 +407,18 @@ def url_encode(url_string):
410407 # Phase accumulates based on instantaneous freq
411408 phase = 0.0
412409 for i in range (num_samples ):
413- t = i / num_samples # 0.0 to 1.0
410+ frac = i / num_samples # 0.0 to 1.0
414411 freq = (BEEP_FREQ_START
415- + (BEEP_FREQ_END - BEEP_FREQ_START ) * t )
412+ + (BEEP_FREQ_END - BEEP_FREQ_START ) * frac )
416413 phase += 2 * math .pi * freq / sample_rate
417414 val = math .sin (phase )
418415 sample = int (val * 32000 )
419416 wav_file .write (struct .pack ("<h" , sample ))
420417 print ("Chirp WAV generated!" )
418+
419+
420+ try :
421+ generate_chirp_wav ()
421422except (OSError , RuntimeError ) as wav_err :
422423 print ("WAV generation error:" , wav_err )
423424
@@ -454,8 +455,8 @@ def url_encode(url_string):
454455# ============================================
455456# MODE MANAGEMENT
456457# ============================================
457- feed_index = FEED_MODES .index (START_MODE )
458- current_feed = FEED_MODES [feed_index ]
458+ feed_index = FEED_MODES .index (START_MODE ) # pylint: disable=invalid-name
459+ current_feed = FEED_MODES [feed_index ] # pylint: disable=invalid-name
459460
460461# Show startup warnings for missing hardware/settings
461462startup_warnings = []
@@ -468,7 +469,7 @@ def url_encode(url_string):
468469
469470# Force local mode if online features unavailable
470471if not online_available :
471- current_feed = "local"
472+ current_feed = "local" # pylint: disable=invalid-name
472473 feed_index = FEED_MODES .index ("local" )
473474 print ("Online unavailable, forcing local mode" )
474475
@@ -482,18 +483,18 @@ def url_encode(url_string):
482483
483484# Prefetch state - alternate cache files to avoid
484485# overwriting the currently displayed OnDiskBitmap
485- prefetch_ready = False
486- prefetch_title = ""
487- prefetch_file = "/sd/cache2.bmp" # next prefetch target
488- display_file = "/sd/cache.bmp" # currently shown file
486+ prefetch_ready = False # pylint: disable=invalid-name
487+ prefetch_title = "" # pylint: disable=invalid-name
488+ prefetch_file = "/sd/cache2.bmp" # pylint: disable=invalid-name
489+ display_file = "/sd/cache.bmp" # pylint: disable=invalid-name
489490
490491# Cache list of local images
491492local_images = []
492493
493494
494495def load_local_images ():
495496 '''Scan /sd/imgs/ for BMP files.'''
496- global local_images
497+ global local_images # pylint: disable=global-statement
497498 local_images = []
498499 try :
499500 for fname in os .listdir (LOCAL_IMG_PATH ):
@@ -660,8 +661,8 @@ def show_local_image():
660661 gc .collect ()
661662 print ("Local image displayed!" )
662663 return True
663- except (OSError , RuntimeError , MemoryError ) as err :
664- print ("Local image error:" , err )
664+ except (OSError , RuntimeError , MemoryError ) as local_err :
665+ print ("Local image error:" , local_err )
665666 return False
666667
667668
@@ -687,14 +688,14 @@ def show_fallback_local():
687688 attempt , remaining ))
688689 status_text .text = "Retry #%d..." % attempt
689690 try :
690- result = show_online_image ()
691- if result is True :
691+ online_result = show_online_image ()
692+ if online_result is True :
692693 print ("Online image loaded!" )
693694 return True
694- if result == "mode" :
695- return result
696- except ( RuntimeError , Exception ) as err : # pylint: disable=broad-except
697- print ("Retry error:" , err )
695+ if online_result == "mode" :
696+ return online_result
697+ except Exception as retry_err : # pylint: disable=broad-except
698+ print ("Retry error:" , retry_err )
698699 gc .collect ()
699700 # Tap-aware wait between retries
700701 if tap_aware_sleep (3 ):
@@ -743,14 +744,15 @@ def show_online_image():
743744 try :
744745 json_data = json_data ["data" ][0 ]
745746 except (KeyError , IndexError , TypeError ):
746- print ("No CMA data in response" )
747- return False
748-
749- new_title = get_title (json_data )
750- print ("Title:" , new_title )
751-
752- # Validate image URL
753- image_url = validate_image (json_data )
747+ json_data = None
748+
749+ # Validate data and image URL
750+ image_url = None
751+ new_title = ""
752+ if json_data :
753+ new_title = get_title (json_data )
754+ print ("Title:" , new_title )
755+ image_url = validate_image (json_data )
754756 json_data = None
755757 gc .collect ()
756758 if not image_url :
@@ -898,7 +900,7 @@ def display_prefetched():
898900 Swaps cache file roles so next prefetch writes
899901 to the file that is no longer on screen.'''
900902 global prefetch_ready # pylint: disable=global-statement
901- global display_file , prefetch_file
903+ global display_file , prefetch_file # pylint: disable=global-statement
902904 if not prefetch_ready :
903905 return False
904906 wipe_transition (prefetch_file )
@@ -926,7 +928,7 @@ def wait_for_input(duration):
926928 Returns "mode" for screen tap.
927929 Returns "timeout" if time expires.'''
928930 global tilt_start # pylint: disable=global-statement
929- global shake_times
931+ global shake_times # pylint: disable=global-statement
930932 stamp = time .monotonic ()
931933 tap_cooldown = stamp + 1.0 # ignore taps for 1s
932934 btn_was_pressed = False
@@ -967,13 +969,15 @@ def wait_for_input(duration):
967969# MAIN LOOP
968970# ============================================
969971load_local_images ()
970- loopcount = 0
971- errorcount = 0
972+ loopcount = 0 # pylint: disable=invalid-name
973+ errorcount = 0 # pylint: disable=invalid-name
972974
973975print ("Starting art display..." )
974976print ("Mode:" , current_feed )
975977text_area .text = "Mode: " + current_feed
976978
979+ # Main loop variables use snake_case (not module constants)
980+ # pylint: disable=invalid-name
977981while True :
978982 try :
979983 # Check for mode tap at start of each cycle
@@ -1083,7 +1087,7 @@ def wait_for_input(duration):
10831087 or "Expected" in error_str ):
10841088 print ("ESP32 SPI/WiFi error - resetting..." )
10851089 try :
1086- pyportal .network ._wifi .esp .reset ()
1090+ pyportal .network ._wifi .esp .reset () # pylint: disable=protected-access
10871091 print ("ESP32 reset complete, waiting..." )
10881092 if tap_aware_sleep (10 ):
10891093 next_mode (count_taps ())
0 commit comments