Skip to content

Commit ee8b140

Browse files
authored
Merge pull request #1958 from FoamyGuy/busy_sim
adding pyportal busy simulator project
2 parents 81c387c + cf03451 commit ee8b140

24 files changed

+231
-0
lines changed
76.1 KB
Binary file not shown.

PyPortal_BusySimulator/code.py

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# SPDX-FileCopyrightText: 2020 Tim C, written for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: Unlicense
4+
"""
5+
PyPortal implementation of Busy Simulator notification sound looper.
6+
"""
7+
import time
8+
import board
9+
import displayio
10+
import adafruit_touchscreen
11+
from adafruit_displayio_layout.layouts.grid_layout import GridLayout
12+
from adafruit_displayio_layout.widgets.icon_widget import IconWidget
13+
from audiocore import WaveFile
14+
from audioio import AudioOut
15+
16+
# How many seconds to wait between playing samples
17+
# Lower time means it will play faster
18+
WAIT_TIME = 3.0
19+
20+
# List that will hold indexes of notification samples to play
21+
LOOP = []
22+
23+
# last time that we played a sample
24+
LAST_PLAY_TIME = 0
25+
26+
CUR_LOOP_INDEX = 0
27+
28+
# touch events must have at least this long between them
29+
COOLDOWN_TIME = 0.25 # seconds
30+
31+
# last time that the display was touched
32+
# used for debouncing and cooldown enforcement
33+
LAST_PRESS_TIME = -1
34+
35+
# Was any icon touched last iteration.
36+
# Used for debouncing.
37+
WAS_TOUCHED = False
38+
39+
40+
def next_index():
41+
"""
42+
return the next index in the LOOP that should get played
43+
"""
44+
if CUR_LOOP_INDEX + 1 >= len(LOOP):
45+
return 0
46+
47+
return CUR_LOOP_INDEX + 1
48+
49+
50+
# list of icons to show
51+
# each entry is a tuple containing:
52+
# (Icon Label, Icon BMP image file, Notification sample wav file
53+
_icons = [
54+
("Outlook", "icons/outlook.bmp", "sounds/outlook.wav"),
55+
("Phone", "icons/phone.bmp", "sounds/phone.wav"),
56+
("Skype", "icons/skype.bmp", "sounds/skype.wav"),
57+
("Teams", "icons/teams.bmp", "sounds/teams.wav"),
58+
("Discord", "icons/discord.bmp", "sounds/discord.wav"),
59+
("Apple Mail", "icons/applemail.bmp", "sounds/applemail.wav"),
60+
("iMessage", "icons/imessage.bmp", "sounds/imessage.wav"),
61+
("Slack", "icons/slack.bmp", "sounds/slack.wav"),
62+
("G Calendar", "icons/gcal.bmp", "sounds/RE.wav"),
63+
("G Chat", "icons/gchat.bmp", "sounds/gchat.wav"),
64+
("Stop", "icons/stop.bmp", ""),
65+
]
66+
67+
# Make the display context.
68+
display = board.DISPLAY
69+
main_group = displayio.Group()
70+
display.show(main_group)
71+
72+
# Touchscreen initialization
73+
ts = adafruit_touchscreen.Touchscreen(
74+
board.TOUCH_XL,
75+
board.TOUCH_XR,
76+
board.TOUCH_YD,
77+
board.TOUCH_YU,
78+
calibration=((5200, 59000), (5800, 57000)),
79+
size=(display.width, display.height),
80+
)
81+
82+
# Setup the file as the bitmap data source
83+
bg_bitmap = displayio.OnDiskBitmap("busysim_background.bmp")
84+
85+
# Create a TileGrid to hold the bitmap
86+
bg_tile_grid = displayio.TileGrid(
87+
bg_bitmap,
88+
pixel_shader=getattr(bg_bitmap, "pixel_shader", displayio.ColorConverter()),
89+
)
90+
91+
# add it to the group that is showing
92+
main_group.append(bg_tile_grid)
93+
94+
# grid to hold the icons
95+
layout = GridLayout(
96+
x=0,
97+
y=0,
98+
width=320,
99+
height=240,
100+
grid_size=(4, 3),
101+
cell_padding=20,
102+
)
103+
104+
# initialize the icons in the grid
105+
for i, icon in enumerate(_icons):
106+
icon_widget = IconWidget(
107+
icon[0],
108+
icon[1],
109+
x=0,
110+
y=0,
111+
on_disk=True,
112+
transparent_index=0,
113+
label_background=0x888888,
114+
)
115+
116+
layout.add_content(icon_widget, grid_position=(i % 4, i // 4), cell_size=(1, 1))
117+
118+
# add the grid to the group showing on the display
119+
main_group.append(layout)
120+
121+
122+
def check_for_touch(_now):
123+
"""
124+
Check the touchscreen and do any actions necessary if an
125+
icon has been touched. Applies debouncing and cool down
126+
enforcement to filter out unneeded touch events.
127+
128+
:param int _now: The current time in seconds. Used for cool down enforcement
129+
"""
130+
# pylint: disable=global-statement, too-many-nested-blocks, consider-using-enumerate
131+
global CUR_LOOP_INDEX
132+
global LOOP
133+
global LAST_PRESS_TIME
134+
global WAS_TOUCHED
135+
136+
# read the touch data
137+
touch_point = ts.touch_point
138+
139+
# if anything is touched
140+
if touch_point:
141+
# if the touch just began. We ignore further events until
142+
# after the touch has been lifted
143+
if not WAS_TOUCHED:
144+
145+
# set the variable so we know to ignore future events until
146+
# touch is released
147+
WAS_TOUCHED = True
148+
149+
# if it has been long enough time since previous touch event
150+
if _now - LAST_PRESS_TIME > COOLDOWN_TIME:
151+
152+
LAST_PRESS_TIME = time.monotonic()
153+
154+
# loop over the icons
155+
for _ in range(len(_icons)):
156+
# lookup current icon in the grid layout
157+
cur_icon = layout.get_cell((_ % 4, _ // 4))
158+
159+
# check if it's being touched
160+
if cur_icon.contains(touch_point):
161+
print("icon {} touched".format(_))
162+
163+
# if it's the stop icon
164+
if _icons[_][0] == "Stop":
165+
166+
# empty out the loop
167+
LOOP = []
168+
169+
# set current index back to 0
170+
CUR_LOOP_INDEX = 0
171+
172+
else: # any other icon
173+
# insert the touched icons sample index into the loop
174+
LOOP.insert(CUR_LOOP_INDEX, _)
175+
176+
# print(LOOP)
177+
178+
# break out of the for loop.
179+
# if current icon is being touched then no others can be
180+
break
181+
182+
# nothing is touched
183+
else:
184+
# set variable back to false for debouncing
185+
WAS_TOUCHED = False
186+
187+
188+
# main loop
189+
while True:
190+
# store current time in variable for cool down enforcement
191+
_now = time.monotonic()
192+
193+
# check for and process touch events
194+
check_for_touch(_now)
195+
196+
# if it's time to play a sample
197+
if LAST_PLAY_TIME + WAIT_TIME <= _now:
198+
# print("time to play")
199+
200+
# if there are any samples in the loop
201+
if len(LOOP) > 0:
202+
203+
# open the sample wav file
204+
with open(_icons[LOOP[CUR_LOOP_INDEX]][2], "rb") as wave_file:
205+
print("playing: {}".format(_icons[LOOP[CUR_LOOP_INDEX]][2]))
206+
207+
# initialize audio output pin
208+
audio = AudioOut(board.AUDIO_OUT)
209+
210+
# initialize WaveFile object
211+
wave = WaveFile(wave_file)
212+
213+
# play it
214+
audio.play(wave)
215+
216+
# while it's still playing
217+
while audio.playing:
218+
# update time variable
219+
_now = time.monotonic()
220+
221+
# check for and process touch events
222+
check_for_touch(_now)
223+
224+
# after done playing. deinit audio output
225+
audio.deinit()
226+
227+
# increment index counter
228+
CUR_LOOP_INDEX = next_index()
229+
230+
# update variable for last time we attempted to play sample
231+
LAST_PLAY_TIME = _now
4.13 KB
Binary file not shown.
2.26 KB
Binary file not shown.

PyPortal_BusySimulator/icons/gcal.bmp

1.35 KB
Binary file not shown.
698 Bytes
Binary file not shown.
1.53 KB
Binary file not shown.
1.79 KB
Binary file not shown.
2.02 KB
Binary file not shown.
1.82 KB
Binary file not shown.
1.67 KB
Binary file not shown.

PyPortal_BusySimulator/icons/stop.bmp

274 Bytes
Binary file not shown.
1.54 KB
Binary file not shown.

PyPortal_BusySimulator/sounds/RE.wav

43.8 KB
Binary file not shown.
25.8 KB
Binary file not shown.
10.8 KB
Binary file not shown.
22.8 KB
Binary file not shown.
90 KB
Binary file not shown.
90.3 KB
Binary file not shown.
56.6 KB
Binary file not shown.
13.7 KB
Binary file not shown.
9.3 KB
Binary file not shown.
17.4 KB
Binary file not shown.
22 KB
Binary file not shown.

0 commit comments

Comments
 (0)