Skip to content

Commit 2375d4b

Browse files
committed
Update neopixel behavior
1 parent 4167c6f commit 2375d4b

File tree

2 files changed

+75
-31
lines changed

2 files changed

+75
-31
lines changed

Magic_AI_Storybook/story.py

Lines changed: 74 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import math
1313
import configparser
1414
from enum import Enum
15-
from tempfile import NamedTemporaryFile
15+
from collections import deque
1616

1717
import board
1818
import digitalio
@@ -30,13 +30,18 @@
3030
API_KEYS_FILE = "~/keys.txt"
3131
PROMPT_FILE = "/boot/bookprompt.txt"
3232

33+
# Quit Settings (Close book QUIT_CLOSES within QUIT_TIME_PERIOD to quit)
34+
QUIT_CLOSES = 3
35+
QUIT_TIME_PERIOD = 5 # Time period in Seconds
36+
3337
# Neopixel Settings
3438
NEOPIXEL_COUNT = 10
3539
NEOPIXEL_BRIGHTNESS = 0.2
3640
NEOPIXEL_ORDER = neopixel.GRBW
37-
NEOPIXEL_SLEEP_COLOR = (0, 0, 255, 0)
38-
NEOPIXEL_WAITING_COLOR = (255, 255, 0, 0)
39-
NEOPIXEL_READY_COLOR = (0, 255, 0, 0)
41+
NEOPIXEL_LOADING_COLOR = (0, 255, 0, 0) # Loading/Dreaming (Green)
42+
NEOPIXEL_SLEEP_COLOR = (0, 0, 0, 0) # Sleeping (Off)
43+
NEOPIXEL_WAITING_COLOR = (255, 255, 0, 0) # Waiting for Input (Yellow)
44+
NEOPIXEL_READING_COLOR = (0, 0, 255, 0) # Reading (Blue)
4045
NEOPIXEL_PULSE_SPEED = 0.1
4146

4247
# Image Names
@@ -62,7 +67,6 @@
6267

6368
# Delays to control the speed of the text
6469
WORD_DELAY = 0.1
65-
WELCOME_IMAGE_DELAY = 0
6670
TITLE_FADE_TIME = 0.05
6771
TITLE_FADE_STEPS = 25
6872
TEXT_FADE_TIME = 0.25
@@ -194,6 +198,9 @@ def __init__(self, rotation=0):
194198
self._sleep_request = False
195199
self._running = True
196200
self._busy = False
201+
self._loading = False
202+
# Use a Double Ended Queue to handle the heavy lifting
203+
self._closing_times = deque(maxlen=QUIT_CLOSES)
197204
# Use a cursor to keep track of where we are in the text area
198205
self.cursor = {"x": 0, "y": 0}
199206
self.listener = None
@@ -206,13 +213,14 @@ def __init__(self, rotation=0):
206213
auto_write=False,
207214
)
208215
self._prompt = ""
209-
# Load the prompt file
210-
with open(PROMPT_FILE, "r") as f:
211-
self._prompt = f.read()
216+
self._load_thread = threading.Thread(target=self._handle_loading_status)
217+
self._load_thread.start()
212218

213219
def start(self):
214220
# Output to the LCD instead of the console
215-
#os.putenv("DISPLAY", ":0")
221+
os.putenv("DISPLAY", ":0")
222+
223+
self._set_status_color(NEOPIXEL_LOADING_COLOR)
216224

217225
# Initialize the display
218226
pygame.init()
@@ -225,7 +233,10 @@ def start(self):
225233
# Preload welcome image and display it
226234
self._load_image("welcome", WELCOME_IMAGE)
227235
self.display_welcome()
228-
start_time = time.monotonic()
236+
237+
# Load the prompt file
238+
with open(PROMPT_FILE, "r") as f:
239+
self._prompt = f.read()
229240

230241
#Initialize the Listener
231242
self.listener = Listener(openai.api_key, ENERGY_THRESHOLD, RECORD_TIMEOUT)
@@ -297,28 +308,18 @@ def start(self):
297308
self._sleep_check_thread = threading.Thread(target=self._handle_sleep)
298309
self._sleep_check_thread.start()
299310

300-
# Light the neopixels to indicate the book is ready
301-
self.pixels.fill(NEOPIXEL_READY_COLOR)
302-
self.pixels.show()
303-
304-
# Continue showing the image until the minimum amount of time has passed
305-
time.sleep(max(0, WELCOME_IMAGE_DELAY - (time.monotonic() - start_time)))
311+
self._set_status_color(NEOPIXEL_READING_COLOR)
306312

307313
def deinit(self):
308314
self._running = False
309315
self._sleep_check_thread.join()
316+
self._load_thread.join()
310317
self.backlight.power = True
311318

312319
def _handle_sleep(self):
313320
reed_switch = digitalio.DigitalInOut(REED_SWITCH_PIN)
314321
reed_switch.direction = digitalio.Direction.INPUT
315322
reed_switch.pull = digitalio.Pull.UP
316-
pulse = Pulse(
317-
self.pixels,
318-
speed=NEOPIXEL_PULSE_SPEED,
319-
color=NEOPIXEL_SLEEP_COLOR,
320-
period=3,
321-
)
322323

323324
while self._running:
324325
if self._sleeping and reed_switch.value: # Book Open
@@ -328,10 +329,37 @@ def _handle_sleep(self):
328329
): # Book Closed
329330
self._sleep()
330331

331-
if self._sleeping:
332-
pulse.animate()
333332
time.sleep(self.sleep_check_delay)
334333

334+
def _handle_loading_status(self):
335+
pulse = Pulse(
336+
self.pixels,
337+
speed=NEOPIXEL_PULSE_SPEED,
338+
color=NEOPIXEL_LOADING_COLOR,
339+
period=3,
340+
)
341+
342+
while self._running:
343+
if self._loading:
344+
pulse.animate()
345+
time.sleep(0.1)
346+
347+
# Turn off the Neopixels
348+
self.pixels.fill(0)
349+
self.pixels.show()
350+
351+
def _set_status_color(self, status_color):
352+
if status_color not in [NEOPIXEL_READING_COLOR, NEOPIXEL_WAITING_COLOR, NEOPIXEL_SLEEP_COLOR, NEOPIXEL_LOADING_COLOR]:
353+
raise ValueError(f"Invalid status color {status_color}.")
354+
355+
# Handle loading color by setting the loading flag
356+
self._loading = status_color == NEOPIXEL_LOADING_COLOR
357+
358+
# Handle other status colors by setting the neopixels
359+
if status_color != NEOPIXEL_LOADING_COLOR:
360+
self.pixels.fill(status_color)
361+
self.pixels.show()
362+
335363
def handle_events(self):
336364
if not self._sleeping:
337365
for event in pygame.event.get():
@@ -514,6 +542,7 @@ def new_story(self):
514542
def display_loading(self):
515543
self._display_surface(self.images["loading"], 0, 0)
516544
pygame.display.update()
545+
self._set_status_color(NEOPIXEL_LOADING_COLOR)
517546

518547
def display_welcome(self):
519548
self._display_surface(self.images["welcome"], 0, 0)
@@ -556,6 +585,7 @@ def load_story(self, story):
556585
if self.cursor["y"] > 0:
557586
self.cursor["y"] += PARAGRAPH_SPACING
558587
print(f"Loaded story at index {self.story} with {len(self.pages)} pages")
588+
self._set_status_color(NEOPIXEL_READING_COLOR)
559589
self._busy = False
560590

561591
def _add_page(self, title=None):
@@ -583,13 +613,12 @@ def generate_new_story(self):
583613
def show_waiting():
584614
# Pause for a beat because the listener doesn't
585615
# immediately start listening sometimes
586-
time.sleep(2)
616+
time.sleep(1)
587617
self.pixels.fill(NEOPIXEL_WAITING_COLOR)
588618
self.pixels.show()
589619

590620
self.listener.listen(ready_callback=show_waiting)
591-
self.pixels.fill(NEOPIXEL_READY_COLOR)
592-
self.pixels.show()
621+
593622
if self._sleep_request:
594623
self._busy = False
595624
return
@@ -623,7 +652,20 @@ def _sleep(self):
623652
while self._busy:
624653
time.sleep(0.1)
625654
self._sleep_request = False
655+
656+
self._closing_times.append(time.monotonic())
657+
658+
# Check if we've closed the book a certain number of times
659+
# within a certain number of seconds
660+
if (
661+
len(self._closing_times) == QUIT_CLOSES
662+
and self._closing_times[-1] - self._closing_times[0] < QUIT_TIME_PERIOD
663+
):
664+
self._running = False
665+
return
666+
626667
self._sleeping = True
668+
self._set_status_color(NEOPIXEL_SLEEP_COLOR)
627669
self.sleep_check_delay = 0
628670
self.saved_screen = self.screen.copy()
629671
self.screen.fill((0, 0, 0))
@@ -638,8 +680,7 @@ def _wake(self):
638680
pygame.display.update()
639681
self.saved_screen = None
640682
self.sleep_check_delay = 0.1
641-
self.pixels.fill(NEOPIXEL_READY_COLOR)
642-
self.pixels.show()
683+
self._set_status_color(NEOPIXEL_READING_COLOR)
643684
self._sleeping = False
644685

645686
def _make_story_prompt(self, request):
@@ -669,6 +710,9 @@ def _sendchat(self, prompt):
669710
# Send the heard text to ChatGPT and return the result
670711
return strip_fancy_quotes(response)
671712

713+
@property
714+
def running(self):
715+
return self._running
672716

673717
def parse_args():
674718
parser = argparse.ArgumentParser()
@@ -692,7 +736,7 @@ def main(args):
692736
book.generate_new_story()
693737
book.display_current_page()
694738

695-
while True:
739+
while book.running:
696740
book.handle_events()
697741
except KeyboardInterrupt:
698742
pass

Magic_AI_Storybook/storybook.desktop

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
Comment=Run Magic Storybook
33
Terminal=true
44
Name=Magic Storybook
5-
Exec=sudo python -E /home/pi/Magic_AI_Storybook/story.py
5+
Exec=sudo python /home/pi/Magic_AI_Storybook/story.py
66
Type=Application
77
Icon=/home/pi/Magic_AI_Storybook/images/magic_book_icon.png

0 commit comments

Comments
 (0)