Skip to content

Commit f450eb9

Browse files
committed
Core code
1 parent 285bc7f commit f450eb9

3 files changed

Lines changed: 7341 additions & 0 deletions

File tree

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
"""
2+
Halloween countdown for PyPortal.
3+
4+
Adafruit invests time and resources providing this open source code.
5+
Please support Adafruit and open source hardware by purchasing
6+
products from Adafruit!
7+
8+
Written by Dave Astels for Adafruit Industries
9+
Copyright (c) 2019 Adafruit Industries
10+
Licensed under the MIT license.
11+
12+
All text above must be included in any redistribution.
13+
"""
14+
15+
import time
16+
import board
17+
import busio
18+
import binascii
19+
import json
20+
from adafruit_pyportal import PyPortal
21+
from adafruit_bitmap_font import bitmap_font
22+
from adafruit_display_text.label import Label
23+
from digitalio import DigitalInOut
24+
from adafruit_esp32spi import adafruit_esp32spi
25+
import adafruit_esp32spi.adafruit_esp32spi_requests as requests
26+
import adafruit_logging as logging
27+
28+
logger = logging.getLogger('main')
29+
logger.setLevel(logging.DEBUG)
30+
try:
31+
from secrets import secrets
32+
except ImportError:
33+
logger.critical("""WiFi settings are kept in secrets.py, please add them there!
34+
the secrets dictionary must contain 'ssid' and 'password' at a minimum""")
35+
raise
36+
37+
def halt_and_catch_fire(message, *args):
38+
"""Log a critical error and stall the system."""
39+
logger.critical(message, *args)
40+
while True:
41+
pass
42+
43+
def connect_esp():
44+
"""Connect the ESP to the network."""
45+
pyportal.neo_status((0, 0, 100))
46+
while not esp.is_connected:
47+
# secrets dictionary must contain 'ssid' and 'password' at a minimum
48+
logger.debug("Connecting to AP %s", secrets['ssid'])
49+
if secrets['ssid'] == 'CHANGE ME' or secrets['ssid'] == 'CHANGE ME':
50+
change_me = "\n"+"*"*45
51+
change_me += "\nPlease update the 'secrets.py' file on your\n"
52+
change_me += "CIRCUITPY drive to include your local WiFi\n"
53+
change_me += "access point SSID name in 'ssid' and SSID\n"
54+
change_me += "password in 'password'. Then save to reload!\n"
55+
change_me += "*"*45
56+
raise OSError(change_me)
57+
pyportal.neo_status((100, 0, 0)) # red = not connected
58+
try:
59+
esp.connect(secrets)
60+
except RuntimeError as error:
61+
logger.error("Could not connect to internet: %s", error)
62+
logger.error("Retrying in 3 seconds...")
63+
time.sleep(3)
64+
65+
#pylint:disable=redefined-outer-name
66+
def get_bearer_token():
67+
"""Get the bearer authentication token from twitter."""
68+
logger.debug('Getting bearer token')
69+
raw_key = secrets['twitter_api_key'] + ':' + secrets['twitter_secret_key']
70+
encoded_key = binascii.b2a_base64(bytes(raw_key, 'utf8'))
71+
string_key = bytes.decode(encoded_key).strip()
72+
headers= {'Authorization': 'Basic ' + string_key,
73+
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'}
74+
response = requests.post('https://api.twitter.com/oauth2/token',
75+
headers=headers,
76+
data='grant_type=client_credentials')
77+
response_dict = json.loads(response.content)
78+
if response_dict['token_type'] != 'bearer':
79+
halt_and_catch_fire('Wrong token type from twitter: %s', response_dict['token_type'])
80+
return response_dict['access_token']
81+
#pylint:enable=redefined-outer-name
82+
83+
#setup esp interface
84+
esp32_ready = DigitalInOut(board.ESP_BUSY)
85+
esp32_gpio0 = DigitalInOut(board.ESP_GPIO0)
86+
esp32_reset = DigitalInOut(board.ESP_RESET)
87+
esp32_cs = DigitalInOut(board.ESP_CS)
88+
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
89+
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready,
90+
esp32_reset, esp32_gpio0)
91+
92+
# determine the current working directory
93+
# needed so we know where to find files
94+
cwd = ("/"+__file__).rsplit('/', 1)[0]
95+
96+
backgrounds = ['countdown_background.bmp']
97+
background_index = 0
98+
event_background = cwd+"/countdown_event.bmp"
99+
100+
# Initialize the pyportal object and let us know what data to fetch and where
101+
# to display it
102+
pyportal = PyPortal(status_neopixel=board.NEOPIXEL,
103+
default_bg=cwd + backgrounds[0],
104+
external_spi=spi,
105+
esp=esp,
106+
caption_text='Days Remaining',
107+
caption_font=cwd+'/fonts/Helvetica-Bold-36.bdf',
108+
caption_position=(13, 160))
109+
110+
# Create the count label
111+
big_font = bitmap_font.load_font(cwd+"/fonts/Helvetica-Bold-36.bdf")
112+
big_font.load_glyphs(b'0123456789') # pre-load glyphs for fast printing
113+
text_color = 0xFFFFFF
114+
countdown_text = Label(big_font, max_glyphs=3)
115+
countdown_text.x = 120
116+
countdown_text.y = 100
117+
countdown_text.color = text_color
118+
pyportal.splash.append(countdown_text)
119+
120+
refresh_time = None
121+
122+
connect_esp()
123+
bearer_token = get_bearer_token()
124+
125+
headers = {'Authorization': 'Bearer ' + bearer_token}
126+
127+
while True:
128+
# only query the online time once per hour (and on first run)
129+
if (not refresh_time) or (time.monotonic() - refresh_time) > 3600:
130+
try:
131+
logger.debug("Getting tweet from internet!")
132+
refresh_time = time.monotonic()
133+
except RuntimeError as e:
134+
logger.error("Some error occured, retrying! -", e)
135+
continue
136+
137+
#fetch the tweetstream from HalloweenCounts
138+
response = requests.get('https://api.twitter.com/1.1/statuses/user_timeline.json?count=1&screen_name=halloweencounts',
139+
headers=headers)
140+
if response.status_code != 200:
141+
logger.error('Tweet fetch status: %d', response.status_code)
142+
else:
143+
timeline = json.loads(response.content)
144+
latest_tweet = timeline[0]
145+
tweet_text = latest_tweet['text']
146+
days_remaining = int(tweet_text.split(' Days Until')[0].split(' ')[-1])
147+
logger.debug('Days remaining: %d', days_remaining)
148+
#if it's halloween, set the bground and stop
149+
if days_remaining == 0:
150+
pyportal.set_background(event_background)
151+
while True:
152+
pass
153+
else:
154+
background_index = (background_index + 1) % len(backgrounds)
155+
pyportal.set_background(backgrounds[background_index])
156+
countdown_text.text = '{:>3}'.format(days_remaining)
157+
158+
# check every minute
159+
time.sleep(60)

0 commit comments

Comments
 (0)