Skip to content

Commit 0448020

Browse files
committed
2 parents 64ad84e + 853802f commit 0448020

File tree

14 files changed

+6311
-0
lines changed

14 files changed

+6311
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import board
2+
import digitalio
3+
4+
from audiomp3 import MP3Decoder
5+
6+
try:
7+
from audioio import AudioOut
8+
except ImportError:
9+
try:
10+
from audiopwmio import PWMAudioOut as AudioOut
11+
except ImportError:
12+
pass # not always supported by every board!
13+
14+
button = digitalio.DigitalInOut(board.A1)
15+
button.switch_to_input(pull=digitalio.Pull.UP)
16+
17+
# The listed mp3files will be played in order
18+
mp3files = ["begins.mp3", "xfiles.mp3"]
19+
20+
# You have to specify some mp3 file when creating the decoder
21+
mp3 = open(mp3files[0], "rb")
22+
decoder = MP3Decoder(mp3)
23+
audio = AudioOut(board.A0)
24+
25+
while True:
26+
for filename in mp3files:
27+
# Updating the .file property of the existing decoder
28+
# helps avoid running out of memory (MemoryError exception)
29+
decoder.file = open(filename, "rb")
30+
audio.play(decoder)
31+
print("playing", filename)
32+
33+
# This allows you to do other things while the audio plays!
34+
while audio.playing:
35+
pass
36+
37+
print("Waiting for button press to continue!")
38+
while button.value:
39+
pass
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import time
2+
import board
3+
import pulseio
4+
5+
led = pulseio.PWMOut(board.D5, frequency=5000, duty_cycle=0)
6+
7+
while True:
8+
for i in range(100):
9+
# PWM LED up and down
10+
if i < 50:
11+
led.duty_cycle = int(i * 2 * 65535 / 100) # Up
12+
else:
13+
led.duty_cycle = 65535 - int((i - 50) * 2 * 65535 / 100) # Down
14+
time.sleep(0.01)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import time
2+
import board
3+
import pulseio
4+
from adafruit_motor import servo
5+
6+
# create a PWMOut object on Pin D5.
7+
pwm = pulseio.PWMOut(board.D5, duty_cycle=2 ** 15, frequency=50)
8+
9+
# Create a servo object.
10+
servo = servo.Servo(pwm)
11+
12+
while True:
13+
for angle in range(0, 180, 5): # 0 - 180 degrees, 5 degrees at a time.
14+
servo.angle = angle
15+
time.sleep(0.05)
16+
for angle in range(180, 0, -5): # 180 - 0 degrees, 5 degrees at a time.
17+
servo.angle = angle
18+
time.sleep(0.05)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import time
2+
import board
3+
import pulseio
4+
5+
# Initialize PWM output for the servo (on pin D5):
6+
servo = pulseio.PWMOut(board.D5, frequency=50)
7+
8+
9+
# Create a function to simplify setting PWM duty cycle for the servo:
10+
def servo_duty_cycle(pulse_ms, frequency=50):
11+
period_ms = 1.0 / frequency * 1000.0
12+
duty_cycle = int(pulse_ms / (period_ms / 65535.0))
13+
return duty_cycle
14+
15+
16+
# Main loop will run forever moving between 1.0 and 2.0 mS long pulses:
17+
while True:
18+
servo.duty_cycle = servo_duty_cycle(1.0)
19+
time.sleep(1.0)
20+
servo.duty_cycle = servo_duty_cycle(2.0)
21+
time.sleep(1.0)
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
from __future__ import print_function
2+
from datetime import datetime
3+
import time
4+
import pickle
5+
import os.path
6+
from googleapiclient.discovery import build
7+
from google_auth_oauthlib.flow import InstalledAppFlow
8+
from google.auth.transport.requests import Request
9+
import textwrap
10+
import digitalio
11+
import busio
12+
import board
13+
from PIL import Image, ImageDraw, ImageFont
14+
from adafruit_epd.epd import Adafruit_EPD
15+
from adafruit_epd.ssd1675 import Adafruit_SSD1675
16+
17+
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
18+
ecs = digitalio.DigitalInOut(board.CE0)
19+
dc = digitalio.DigitalInOut(board.D22)
20+
rst = digitalio.DigitalInOut(board.D27)
21+
busy = digitalio.DigitalInOut(board.D17)
22+
up_button = digitalio.DigitalInOut(board.D5)
23+
up_button.switch_to_input()
24+
down_button = digitalio.DigitalInOut(board.D6)
25+
down_button.switch_to_input()
26+
27+
# If modifying these scopes, delete the file token.pickle.
28+
SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"]
29+
30+
# Check for new/deleted events every 10 seconds
31+
QUERY_DELAY = 10 # Time in seconds to delay between querying the Google Calendar API
32+
MAX_EVENTS_PER_CAL = 5
33+
MAX_LINES = 2
34+
DEBOUNCE_DELAY = 0.3
35+
36+
# Initialize the Display
37+
display = Adafruit_SSD1675(
38+
122, 250, spi, cs_pin=ecs, dc_pin=dc, sramcs_pin=None, rst_pin=rst, busy_pin=busy,
39+
)
40+
41+
display.rotation = 3
42+
43+
# RGB Colors
44+
WHITE = (255, 255, 255)
45+
BLACK = (0, 0, 0)
46+
47+
creds = None
48+
# The file token.pickle stores the user's access and refresh tokens, and is
49+
# created automatically when the authorization flow completes for the first
50+
# time.
51+
if os.path.exists("token.pickle"):
52+
with open("token.pickle", "rb") as token:
53+
creds = pickle.load(token)
54+
# If there are no (valid) credentials available, let the user log in.
55+
if not creds or not creds.valid:
56+
if creds and creds.expired and creds.refresh_token:
57+
creds.refresh(Request())
58+
else:
59+
flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES)
60+
creds = flow.run_console()
61+
# Save the credentials for the next run
62+
with open("token.pickle", "wb") as token:
63+
pickle.dump(creds, token)
64+
65+
service = build("calendar", "v3", credentials=creds)
66+
67+
current_event_id = None
68+
last_check = None
69+
events = []
70+
71+
72+
def display_event(event_id):
73+
event_index = search_id(event_id)
74+
if event_index is None:
75+
if len(events) > 0:
76+
# Event was probably deleted while we were updating
77+
event_index = 0
78+
event = events[0]
79+
else:
80+
event = None
81+
else:
82+
event = events[event_index]
83+
84+
current_time = get_current_time()
85+
display.fill(Adafruit_EPD.WHITE)
86+
image = Image.new("RGB", (display.width, display.height), color=WHITE)
87+
draw = ImageDraw.Draw(image)
88+
event_font = ImageFont.truetype(
89+
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 24
90+
)
91+
time_font = ImageFont.truetype(
92+
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 18
93+
)
94+
next_event_font = ImageFont.truetype(
95+
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16
96+
)
97+
98+
# Draw Time
99+
current_time = get_current_time()
100+
(font_width, font_height) = time_font.getsize(current_time)
101+
draw.text(
102+
(display.width - font_width - 2, 2), current_time, font=time_font, fill=BLACK,
103+
)
104+
105+
if event is None:
106+
text = "No events found"
107+
(font_width, font_height) = event_font.getsize(text)
108+
draw.text(
109+
(
110+
display.width // 2 - font_width // 2,
111+
display.height // 2 - font_height // 2,
112+
),
113+
text,
114+
font=event_font,
115+
fill=BLACK,
116+
)
117+
else:
118+
how_long = format_interval(
119+
event["start"].get("dateTime", event["start"].get("date"))
120+
)
121+
draw.text(
122+
(2, 2), how_long, font=time_font, fill=BLACK,
123+
)
124+
125+
(font_width, font_height) = event_font.getsize(event["summary"])
126+
lines = textwrap.wrap(event["summary"], width=20)
127+
for line_index, line in enumerate(lines):
128+
if line_index < MAX_LINES:
129+
draw.text(
130+
(2, line_index * font_height + 22),
131+
line,
132+
font=event_font,
133+
fill=BLACK,
134+
)
135+
136+
# Draw Next Event if there is one
137+
if event_index < len(events) - 1:
138+
next_event = events[event_index + 1]
139+
next_time = format_event_date(
140+
next_event["start"].get("dateTime", next_event["start"].get("date"))
141+
)
142+
next_item = "Then " + next_time + ": "
143+
(font_width, font_height) = next_event_font.getsize(next_item)
144+
draw.text(
145+
(2, display.height - font_height * 2 - 8),
146+
next_item,
147+
font=next_event_font,
148+
fill=BLACK,
149+
)
150+
draw.text(
151+
(2, display.height - font_height - 2),
152+
next_event["summary"],
153+
font=next_event_font,
154+
fill=BLACK,
155+
)
156+
157+
display.image(image)
158+
display.display()
159+
160+
161+
def format_event_date(datestr):
162+
event_date = datetime.fromisoformat(datestr)
163+
# If the same day, just return time
164+
if event_date.date() == datetime.now().date():
165+
return event_date.strftime("%I:%M %p")
166+
# If a future date, return date and time
167+
return event_date.strftime("%m/%d/%y %I:%M %p")
168+
169+
170+
def format_interval(datestr):
171+
event_date = datetime.fromisoformat(datestr).replace(tzinfo=None)
172+
delta = event_date - datetime.now()
173+
# if < 60 minutes, return minutes
174+
if delta.days < 0:
175+
return "Now:"
176+
if not delta.days and delta.seconds < 3600:
177+
value = round(delta.seconds / 60)
178+
return "In {} minute{}:".format(value, "s" if value > 1 else "")
179+
# if < 24 hours return hours
180+
if not delta.days:
181+
value = round(delta.seconds / 3600)
182+
return "In {} hour{}:".format(value, "s" if value > 1 else "")
183+
return "In {} day{}:".format(delta.days, "s" if delta.days > 1 else "")
184+
185+
186+
def search_id(event_id):
187+
if event_id is not None:
188+
for index, event in enumerate(events):
189+
if event["id"] == event_id:
190+
return index
191+
return None
192+
193+
194+
def get_current_time():
195+
now = datetime.now()
196+
return now.strftime("%I:%M %p")
197+
198+
199+
current_time = get_current_time()
200+
201+
202+
def get_events(calendar_id):
203+
print("Fetching Events for {}".format(calendar_id))
204+
page_token = None
205+
events = (
206+
service.events()
207+
.list(
208+
calendarId=calendar_id,
209+
timeMin=now,
210+
maxResults=MAX_EVENTS_PER_CAL,
211+
singleEvents=True,
212+
orderBy="startTime",
213+
)
214+
.execute()
215+
)
216+
return events.get("items", [])
217+
218+
219+
def get_all_calendar_ids():
220+
page_token = None
221+
calendar_ids = []
222+
while True:
223+
print("Fetching Calendar IDs")
224+
calendar_list = service.calendarList().list(pageToken=page_token).execute()
225+
for calendar_list_entry in calendar_list["items"]:
226+
calendar_ids.append(calendar_list_entry["id"])
227+
page_token = calendar_list.get("nextPageToken")
228+
if not page_token:
229+
break
230+
return calendar_ids
231+
232+
233+
while True:
234+
last_event_id = current_event_id
235+
last_time = current_time
236+
237+
if last_check is None or time.monotonic() >= last_check + QUERY_DELAY:
238+
# Call the Calendar API
239+
now = datetime.utcnow().isoformat() + "Z"
240+
calendar_ids = get_all_calendar_ids()
241+
events = []
242+
for calendar_id in calendar_ids:
243+
events += get_events(calendar_id)
244+
245+
# Sort Events by Start Time
246+
events = sorted(
247+
events, key=lambda k: k["start"].get("dateTime", k["start"].get("date"))
248+
)
249+
last_check = time.monotonic()
250+
251+
# Update the current time
252+
current_time = get_current_time()
253+
254+
if not events:
255+
current_event_id = None
256+
current_index = None
257+
else:
258+
if current_event_id is None:
259+
current_index = 0
260+
else:
261+
current_index = search_id(current_event_id)
262+
263+
if current_index is not None:
264+
# Check for Button Presses
265+
if up_button.value != down_button.value:
266+
if not up_button.value and current_index < len(events) - 1:
267+
current_index += 1
268+
time.sleep(DEBOUNCE_DELAY)
269+
if not down_button.value and current_index > 0:
270+
current_index -= 1
271+
time.sleep(DEBOUNCE_DELAY)
272+
273+
current_event_id = events[current_index]["id"]
274+
else:
275+
current_event_id = None
276+
if current_event_id != last_event_id or current_time != last_time:
277+
display_event(current_event_id)
23.1 KB
Binary file not shown.

0 commit comments

Comments
 (0)