2323
2424from listener import Listener
2525
26+ # Base Path is the folder the script resides in
27+ BASE_PATH = os .path .dirname (sys .argv [0 ])
28+ if BASE_PATH != "" :
29+ BASE_PATH += "/"
30+
31+ # General Settings
2632STORY_WORD_LENGTH = 800
2733REED_SWITCH_PIN = board .D17
2834NEOPIXEL_PIN = board .D18
3238# Quit Settings (Close book QUIT_CLOSES within QUIT_TIME_PERIOD to quit)
3339QUIT_CLOSES = 3
3440QUIT_TIME_PERIOD = 5 # Time period in Seconds
41+ QUIT_DEBOUNCE_DELAY = 0.25 # Time to wait before counting next closeing
3542
3643# Neopixel Settings
37- NEOPIXEL_COUNT = 10
44+ NEOPIXEL_COUNT = 1
3845NEOPIXEL_BRIGHTNESS = 0.2
3946NEOPIXEL_ORDER = neopixel .GRBW
4047NEOPIXEL_LOADING_COLOR = (0 , 255 , 0 , 0 ) # Loading/Dreaming (Green)
4350NEOPIXEL_READING_COLOR = (0 , 0 , 255 , 0 ) # Reading (Blue)
4451NEOPIXEL_PULSE_SPEED = 0.1
4552
46- # Image Names
53+ # Image Settings
4754WELCOME_IMAGE = "welcome.png"
4855BACKGROUND_IMAGE = "paper_background.png"
4956LOADING_IMAGE = "loading.png"
5259BUTTON_NEW_IMAGE = "button_new.png"
5360
5461# Asset Paths
55- BASE_PATH = os .path .dirname (sys .argv [0 ])
56- if BASE_PATH != "" :
57- BASE_PATH += "/"
5862IMAGES_PATH = BASE_PATH + "images/"
5963FONTS_PATH = BASE_PATH + "fonts/"
6064
61- # Font Path, Size
65+ # Font Path & Size
6266TITLE_FONT = (FONTS_PATH + "Desdemona Black Regular.otf" , 48 )
6367TITLE_COLOR = (0 , 0 , 0 )
6468TEXT_FONT = (FONTS_PATH + "times new roman.ttf" , 24 )
6569TEXT_COLOR = (0 , 0 , 0 )
6670
67- # Delays to control the speed of the text
71+ # Delays Settings
72+ # Used to control the speed of the text
6873WORD_DELAY = 0.1
6974TITLE_FADE_TIME = 0.05
7075TITLE_FADE_STEPS = 25
7176TEXT_FADE_TIME = 0.25
7277TEXT_FADE_STEPS = 51
78+ ALSA_ERROR_DELAY = 1.0 # Delay to wait after an ALSA errors
7379
74- # Whitespace Settings in Pixels
80+ # Whitespace Settings ( in Pixels)
7581PAGE_TOP_MARGIN = 20
7682PAGE_SIDE_MARGIN = 20
7783PAGE_BOTTOM_MARGIN = 0
8591WHISPER_MODEL = "whisper-1"
8692
8793# Speech Recognition Parameters
88- ENERGY_THRESHOLD = 1000 # Energy level for mic to detect
89- PHRASE_TIMEOUT = 3.0 # Space between recordings for sepating phrases
90- RECORD_TIMEOUT = 30
94+ ENERGY_THRESHOLD = 300 # Energy level for mic to detect
95+ RECORD_TIMEOUT = 30 # Maximum time in seconds to wait for speech
9196
9297# Do some checks and Import API keys from API_KEYS_FILE
9398config = configparser .ConfigParser ()
9499
100+ if os .geteuid () != 0 :
101+ print ("Please run this script as root." )
102+ sys .exit (1 )
95103username = os .environ ["SUDO_USER" ]
96104user_homedir = os .path .expanduser (f"~{ username } " )
97105API_KEYS_FILE = API_KEYS_FILE .replace ("~" , user_homedir )
@@ -325,11 +333,8 @@ def _handle_sleep(self):
325333 while self ._running :
326334 if self ._sleeping and reed_switch .value : # Book Open
327335 self ._wake ()
328- elif (
329- not self ._busy and not self ._sleeping and not reed_switch .value
330- ): # Book Closed
336+ elif not self ._sleeping and not reed_switch .value :
331337 self ._sleep ()
332-
333338 time .sleep (self .sleep_check_delay )
334339
335340 def _handle_loading_status (self ):
@@ -429,13 +434,19 @@ def _fade_in_surface(self, surface, x, y, fade_time, fade_steps=50):
429434 fade_time / fade_steps * 1000
430435 ) # Time to delay in ms between each fade step
431436
432- for alpha in range ( 0 , 255 , round ( 255 / fade_steps ) ):
437+ def draw_alpha ( alpha ):
433438 buffer .blit (background , (- x , - y ))
434439 surface .set_alpha (alpha )
435440 buffer .blit (surface , (0 , 0 ))
436441 self ._display_surface (buffer , x , y )
437442 pygame .display .update ()
443+
444+ for alpha in range (0 , 255 , round (255 / fade_steps )):
445+ draw_alpha (alpha )
438446 pygame .time .wait (fade_delay )
447+ if self ._sleep_request :
448+ draw_alpha (255 ) # Finish up quickly
449+ return
439450
440451 def display_current_page (self ):
441452 self ._busy = True
@@ -482,23 +493,33 @@ def _display_title_text(self, text, y=0):
482493 # Render the title as multiple lines if too big
483494 lines = self ._wrap_text (text , self .fonts ["title" ], self .textarea .width )
484495 self .cursor ["y" ] = y
496+ delay_value = WORD_DELAY
485497 for line in lines :
486498 words = line .split (" " )
487499 self .cursor ["x" ] = (
488500 self .textarea .width // 2 - self .fonts ["title" ].size (line )[0 ] // 2
489501 )
490502 for word in words :
491503 text = self .fonts ["title" ].render (word + " " , True , TITLE_COLOR )
492- self ._fade_in_surface (
493- text ,
494- self .cursor ["x" ] + self .textarea .x ,
495- self .cursor ["y" ] + self .textarea .y ,
496- TITLE_FADE_TIME ,
497- TITLE_FADE_STEPS ,
498- )
504+ if self ._sleep_request :
505+ delay_value = 0
506+ self ._display_surface (
507+ text ,
508+ self .cursor ["x" ] + self .textarea .x ,
509+ self .cursor ["y" ] + self .textarea .y ,
510+ )
511+ else :
512+ self ._fade_in_surface (
513+ text ,
514+ self .cursor ["x" ] + self .textarea .x ,
515+ self .cursor ["y" ] + self .textarea .y ,
516+ TITLE_FADE_TIME ,
517+ TITLE_FADE_STEPS ,
518+ )
519+
499520 pygame .display .update ()
500521 self .cursor ["x" ] += text .get_width ()
501- time .sleep (WORD_DELAY )
522+ time .sleep (delay_value )
502523 self .cursor ["y" ] += self .fonts ["title" ].size (line )[1 ]
503524
504525 def _title_text_height (self , text ):
@@ -614,12 +635,14 @@ def generate_new_story(self):
614635
615636 if self ._sleep_request :
616637 self ._busy = False
638+ time .sleep (0.2 )
639+ print ("Not busy anymore" )
617640 return
618641
619642 def show_waiting ():
620643 # Pause for a beat because the listener doesn't
621644 # immediately start listening sometimes
622- time .sleep (1 )
645+ time .sleep (ALSA_ERROR_DELAY )
623646 self .pixels .fill (NEOPIXEL_WAITING_COLOR )
624647 self .pixels .show ()
625648
@@ -654,12 +677,18 @@ def show_waiting():
654677 def _sleep (self ):
655678 # Set a sleep request flag so that any busy threads know to finish up
656679 self ._sleep_request = True
657- self .listener .stop_listening ()
680+ if self .listener .is_listening ():
681+ self .listener .stop_listening ()
658682 while self ._busy :
683+ print ("Still busy" )
659684 time .sleep (0.1 )
660685 self ._sleep_request = False
661686
662- self ._closing_times .append (time .monotonic ())
687+ if (
688+ len (self ._closing_times ) == 0
689+ or (time .monotonic () - self ._closing_times [- 1 ]) > QUIT_DEBOUNCE_DELAY
690+ ):
691+ self ._closing_times .append (time .monotonic ())
663692
664693 # Check if we've closed the book a certain number of times
665694 # within a certain number of seconds
@@ -720,6 +749,10 @@ def _sendchat(self, prompt):
720749 def running (self ):
721750 return self ._running
722751
752+ @property
753+ def sleeping (self ):
754+ return self ._sleeping
755+
723756
724757def parse_args ():
725758 parser = argparse .ArgumentParser ()
@@ -740,7 +773,9 @@ def main(args):
740773 book = Book (args .rotation )
741774 try :
742775 book .start ()
743- book .generate_new_story ()
776+ while len (book .pages ) == 0 :
777+ if not book .sleeping :
778+ book .generate_new_story ()
744779 book .display_current_page ()
745780
746781 while book .running :
0 commit comments