23
23
24
24
from listener import Listener
25
25
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
26
32
STORY_WORD_LENGTH = 800
27
33
REED_SWITCH_PIN = board .D17
28
34
NEOPIXEL_PIN = board .D18
32
38
# Quit Settings (Close book QUIT_CLOSES within QUIT_TIME_PERIOD to quit)
33
39
QUIT_CLOSES = 3
34
40
QUIT_TIME_PERIOD = 5 # Time period in Seconds
41
+ QUIT_DEBOUNCE_DELAY = 0.25 # Time to wait before counting next closeing
35
42
36
43
# Neopixel Settings
37
- NEOPIXEL_COUNT = 10
44
+ NEOPIXEL_COUNT = 1
38
45
NEOPIXEL_BRIGHTNESS = 0.2
39
46
NEOPIXEL_ORDER = neopixel .GRBW
40
47
NEOPIXEL_LOADING_COLOR = (0 , 255 , 0 , 0 ) # Loading/Dreaming (Green)
43
50
NEOPIXEL_READING_COLOR = (0 , 0 , 255 , 0 ) # Reading (Blue)
44
51
NEOPIXEL_PULSE_SPEED = 0.1
45
52
46
- # Image Names
53
+ # Image Settings
47
54
WELCOME_IMAGE = "welcome.png"
48
55
BACKGROUND_IMAGE = "paper_background.png"
49
56
LOADING_IMAGE = "loading.png"
52
59
BUTTON_NEW_IMAGE = "button_new.png"
53
60
54
61
# Asset Paths
55
- BASE_PATH = os .path .dirname (sys .argv [0 ])
56
- if BASE_PATH != "" :
57
- BASE_PATH += "/"
58
62
IMAGES_PATH = BASE_PATH + "images/"
59
63
FONTS_PATH = BASE_PATH + "fonts/"
60
64
61
- # Font Path, Size
65
+ # Font Path & Size
62
66
TITLE_FONT = (FONTS_PATH + "Desdemona Black Regular.otf" , 48 )
63
67
TITLE_COLOR = (0 , 0 , 0 )
64
68
TEXT_FONT = (FONTS_PATH + "times new roman.ttf" , 24 )
65
69
TEXT_COLOR = (0 , 0 , 0 )
66
70
67
- # Delays to control the speed of the text
71
+ # Delays Settings
72
+ # Used to control the speed of the text
68
73
WORD_DELAY = 0.1
69
74
TITLE_FADE_TIME = 0.05
70
75
TITLE_FADE_STEPS = 25
71
76
TEXT_FADE_TIME = 0.25
72
77
TEXT_FADE_STEPS = 51
78
+ ALSA_ERROR_DELAY = 1.0 # Delay to wait after an ALSA errors
73
79
74
- # Whitespace Settings in Pixels
80
+ # Whitespace Settings ( in Pixels)
75
81
PAGE_TOP_MARGIN = 20
76
82
PAGE_SIDE_MARGIN = 20
77
83
PAGE_BOTTOM_MARGIN = 0
85
91
WHISPER_MODEL = "whisper-1"
86
92
87
93
# 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
91
96
92
97
# Do some checks and Import API keys from API_KEYS_FILE
93
98
config = configparser .ConfigParser ()
94
99
100
+ if os .geteuid () != 0 :
101
+ print ("Please run this script as root." )
102
+ sys .exit (1 )
95
103
username = os .environ ["SUDO_USER" ]
96
104
user_homedir = os .path .expanduser (f"~{ username } " )
97
105
API_KEYS_FILE = API_KEYS_FILE .replace ("~" , user_homedir )
@@ -325,11 +333,8 @@ def _handle_sleep(self):
325
333
while self ._running :
326
334
if self ._sleeping and reed_switch .value : # Book Open
327
335
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 :
331
337
self ._sleep ()
332
-
333
338
time .sleep (self .sleep_check_delay )
334
339
335
340
def _handle_loading_status (self ):
@@ -429,13 +434,19 @@ def _fade_in_surface(self, surface, x, y, fade_time, fade_steps=50):
429
434
fade_time / fade_steps * 1000
430
435
) # Time to delay in ms between each fade step
431
436
432
- for alpha in range ( 0 , 255 , round ( 255 / fade_steps ) ):
437
+ def draw_alpha ( alpha ):
433
438
buffer .blit (background , (- x , - y ))
434
439
surface .set_alpha (alpha )
435
440
buffer .blit (surface , (0 , 0 ))
436
441
self ._display_surface (buffer , x , y )
437
442
pygame .display .update ()
443
+
444
+ for alpha in range (0 , 255 , round (255 / fade_steps )):
445
+ draw_alpha (alpha )
438
446
pygame .time .wait (fade_delay )
447
+ if self ._sleep_request :
448
+ draw_alpha (255 ) # Finish up quickly
449
+ return
439
450
440
451
def display_current_page (self ):
441
452
self ._busy = True
@@ -482,23 +493,33 @@ def _display_title_text(self, text, y=0):
482
493
# Render the title as multiple lines if too big
483
494
lines = self ._wrap_text (text , self .fonts ["title" ], self .textarea .width )
484
495
self .cursor ["y" ] = y
496
+ delay_value = WORD_DELAY
485
497
for line in lines :
486
498
words = line .split (" " )
487
499
self .cursor ["x" ] = (
488
500
self .textarea .width // 2 - self .fonts ["title" ].size (line )[0 ] // 2
489
501
)
490
502
for word in words :
491
503
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
+
499
520
pygame .display .update ()
500
521
self .cursor ["x" ] += text .get_width ()
501
- time .sleep (WORD_DELAY )
522
+ time .sleep (delay_value )
502
523
self .cursor ["y" ] += self .fonts ["title" ].size (line )[1 ]
503
524
504
525
def _title_text_height (self , text ):
@@ -614,12 +635,14 @@ def generate_new_story(self):
614
635
615
636
if self ._sleep_request :
616
637
self ._busy = False
638
+ time .sleep (0.2 )
639
+ print ("Not busy anymore" )
617
640
return
618
641
619
642
def show_waiting ():
620
643
# Pause for a beat because the listener doesn't
621
644
# immediately start listening sometimes
622
- time .sleep (1 )
645
+ time .sleep (ALSA_ERROR_DELAY )
623
646
self .pixels .fill (NEOPIXEL_WAITING_COLOR )
624
647
self .pixels .show ()
625
648
@@ -654,12 +677,18 @@ def show_waiting():
654
677
def _sleep (self ):
655
678
# Set a sleep request flag so that any busy threads know to finish up
656
679
self ._sleep_request = True
657
- self .listener .stop_listening ()
680
+ if self .listener .is_listening ():
681
+ self .listener .stop_listening ()
658
682
while self ._busy :
683
+ print ("Still busy" )
659
684
time .sleep (0.1 )
660
685
self ._sleep_request = False
661
686
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 ())
663
692
664
693
# Check if we've closed the book a certain number of times
665
694
# within a certain number of seconds
@@ -720,6 +749,10 @@ def _sendchat(self, prompt):
720
749
def running (self ):
721
750
return self ._running
722
751
752
+ @property
753
+ def sleeping (self ):
754
+ return self ._sleeping
755
+
723
756
724
757
def parse_args ():
725
758
parser = argparse .ArgumentParser ()
@@ -740,7 +773,9 @@ def main(args):
740
773
book = Book (args .rotation )
741
774
try :
742
775
book .start ()
743
- book .generate_new_story ()
776
+ while len (book .pages ) == 0 :
777
+ if not book .sleeping :
778
+ book .generate_new_story ()
744
779
book .display_current_page ()
745
780
746
781
while book .running :
0 commit comments