Non twitter version with images
BIN
PyPortal_Halloween_Countdown/background_1.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_10.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_11.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_12.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_13.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_14.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_15.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_16.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_17.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_18.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_2.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_3.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_4.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_5.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_6.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_7.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_8.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
PyPortal_Halloween_Countdown/background_9.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
|
|
@ -1,99 +0,0 @@
|
|||
PAD = '='
|
||||
|
||||
table_a2b_base64 = [
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
|
||||
52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1, # Note PAD->-1 here
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
|
||||
15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
|
||||
-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
|
||||
41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
]
|
||||
def _transform(n):
|
||||
if n == -1:
|
||||
return '\xff'
|
||||
else:
|
||||
return chr(n)
|
||||
table_a2b_base64 = ''.join(map(_transform, table_a2b_base64))
|
||||
assert len(table_a2b_base64) == 256
|
||||
|
||||
def a2b_base64(ascii):
|
||||
"Decode a line of base64 data."
|
||||
|
||||
res = []
|
||||
quad_pos = 0
|
||||
leftchar = 0
|
||||
leftbits = 0
|
||||
last_char_was_a_pad = False
|
||||
|
||||
for c in ascii:
|
||||
c = chr(c)
|
||||
if c == PAD:
|
||||
if quad_pos > 2 or (quad_pos == 2 and last_char_was_a_pad):
|
||||
break # stop on 'xxx=' or on 'xx=='
|
||||
last_char_was_a_pad = True
|
||||
else:
|
||||
n = ord(table_a2b_base64[ord(c)])
|
||||
if n == 0xff:
|
||||
continue # ignore strange characters
|
||||
#
|
||||
# Shift it in on the low end, and see if there's
|
||||
# a byte ready for output.
|
||||
quad_pos = (quad_pos + 1) & 3
|
||||
leftchar = (leftchar << 6) | n
|
||||
leftbits += 6
|
||||
#
|
||||
if leftbits >= 8:
|
||||
leftbits -= 8
|
||||
res.append((leftchar >> leftbits).to_bytes(1, 'big'))
|
||||
leftchar &= ((1 << leftbits) - 1)
|
||||
#
|
||||
last_char_was_a_pad = False
|
||||
else:
|
||||
if leftbits != 0:
|
||||
raise Exception("Incorrect padding")
|
||||
|
||||
return b''.join(res)
|
||||
|
||||
# ____________________________________________________________
|
||||
|
||||
table_b2a_base64 = (
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
|
||||
|
||||
def b2a_base64(bindata):
|
||||
"Base64-code line of data."
|
||||
|
||||
newlength = (len(bindata) + 2) // 3
|
||||
newlength = newlength * 4 + 1
|
||||
res = []
|
||||
|
||||
leftchar = 0
|
||||
leftbits = 0
|
||||
for c in bindata:
|
||||
# Shift into our buffer, and output any 6bits ready
|
||||
leftchar = (leftchar << 8) | c
|
||||
leftbits += 8
|
||||
res.append(table_b2a_base64[(leftchar >> (leftbits-6)) & 0x3f])
|
||||
leftbits -= 6
|
||||
if leftbits >= 6:
|
||||
res.append(table_b2a_base64[(leftchar >> (leftbits-6)) & 0x3f])
|
||||
leftbits -= 6
|
||||
#
|
||||
if leftbits == 2:
|
||||
res.append(table_b2a_base64[(leftchar & 3) << 4])
|
||||
res.append(PAD)
|
||||
res.append(PAD)
|
||||
elif leftbits == 4:
|
||||
res.append(table_b2a_base64[(leftchar & 0xf) << 2])
|
||||
res.append(PAD)
|
||||
res.append('\n')
|
||||
return ''.join(res).encode('ascii')
|
||||
|
|
@ -1,159 +1,117 @@
|
|||
"""
|
||||
Halloween countdown for PyPortal.
|
||||
|
||||
Adafruit invests time and resources providing this open source code.
|
||||
Please support Adafruit and open source hardware by purchasing
|
||||
products from Adafruit!
|
||||
|
||||
Written by Dave Astels for Adafruit Industries
|
||||
Copyright (c) 2019 Adafruit Industries
|
||||
Licensed under the MIT license.
|
||||
|
||||
All text above must be included in any redistribution.
|
||||
This example will figure out the current local time using the internet, and
|
||||
then draw out a countdown clock until an event occurs!
|
||||
Once the event is happening, a new graphic is shown
|
||||
"""
|
||||
|
||||
import time
|
||||
import random
|
||||
import board
|
||||
import busio
|
||||
import binascii
|
||||
import json
|
||||
from adafruit_pyportal import PyPortal
|
||||
from adafruit_bitmap_font import bitmap_font
|
||||
from adafruit_display_text.label import Label
|
||||
from digitalio import DigitalInOut
|
||||
from adafruit_esp32spi import adafruit_esp32spi
|
||||
import adafruit_esp32spi.adafruit_esp32spi_requests as requests
|
||||
import adafruit_logging as logging
|
||||
|
||||
logger = logging.getLogger('main')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
try:
|
||||
from secrets import secrets
|
||||
except ImportError:
|
||||
logger.critical("""WiFi settings are kept in secrets.py, please add them there!
|
||||
print("""WiFi settings are kept in secrets.py, please add them there!
|
||||
the secrets dictionary must contain 'ssid' and 'password' at a minimum""")
|
||||
raise
|
||||
|
||||
def halt_and_catch_fire(message, *args):
|
||||
"""Log a critical error and stall the system."""
|
||||
logger.critical(message, *args)
|
||||
while True:
|
||||
pass
|
||||
|
||||
def connect_esp():
|
||||
"""Connect the ESP to the network."""
|
||||
pyportal.neo_status((0, 0, 100))
|
||||
while not esp.is_connected:
|
||||
# secrets dictionary must contain 'ssid' and 'password' at a minimum
|
||||
logger.debug("Connecting to AP %s", secrets['ssid'])
|
||||
if secrets['ssid'] == 'CHANGE ME' or secrets['ssid'] == 'CHANGE ME':
|
||||
change_me = "\n"+"*"*45
|
||||
change_me += "\nPlease update the 'secrets.py' file on your\n"
|
||||
change_me += "CIRCUITPY drive to include your local WiFi\n"
|
||||
change_me += "access point SSID name in 'ssid' and SSID\n"
|
||||
change_me += "password in 'password'. Then save to reload!\n"
|
||||
change_me += "*"*45
|
||||
raise OSError(change_me)
|
||||
pyportal.neo_status((100, 0, 0)) # red = not connected
|
||||
try:
|
||||
esp.connect(secrets)
|
||||
except RuntimeError as error:
|
||||
logger.error("Could not connect to internet: %s", error)
|
||||
logger.error("Retrying in 3 seconds...")
|
||||
time.sleep(3)
|
||||
|
||||
#pylint:disable=redefined-outer-name
|
||||
def get_bearer_token():
|
||||
"""Get the bearer authentication token from twitter."""
|
||||
logger.debug('Getting bearer token')
|
||||
raw_key = secrets['twitter_api_key'] + ':' + secrets['twitter_secret_key']
|
||||
encoded_key = binascii.b2a_base64(bytes(raw_key, 'utf8'))
|
||||
string_key = bytes.decode(encoded_key).strip()
|
||||
headers= {'Authorization': 'Basic ' + string_key,
|
||||
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'}
|
||||
response = requests.post('https://api.twitter.com/oauth2/token',
|
||||
headers=headers,
|
||||
data='grant_type=client_credentials')
|
||||
response_dict = json.loads(response.content)
|
||||
if response_dict['token_type'] != 'bearer':
|
||||
halt_and_catch_fire('Wrong token type from twitter: %s', response_dict['token_type'])
|
||||
return response_dict['access_token']
|
||||
#pylint:enable=redefined-outer-name
|
||||
|
||||
#setup esp interface
|
||||
esp32_ready = DigitalInOut(board.ESP_BUSY)
|
||||
esp32_gpio0 = DigitalInOut(board.ESP_GPIO0)
|
||||
esp32_reset = DigitalInOut(board.ESP_RESET)
|
||||
esp32_cs = DigitalInOut(board.ESP_CS)
|
||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready,
|
||||
esp32_reset, esp32_gpio0)
|
||||
# The time of the thing!
|
||||
EVENT_YEAR = 2019
|
||||
EVENT_MONTH = 10
|
||||
EVENT_DAY = 31
|
||||
# we'll make a python-friendly structure
|
||||
event_time = time.struct_time((EVENT_YEAR, EVENT_MONTH, EVENT_DAY,
|
||||
0, 0, 0, # we don't track hours, minutes, or seconds
|
||||
-1, -1, False)) # we dont know day of week/year or DST
|
||||
|
||||
# determine the current working directory
|
||||
# needed so we know where to find files
|
||||
cwd = ("/"+__file__).rsplit('/', 1)[0]
|
||||
|
||||
backgrounds = ['countdown_background.bmp']
|
||||
background_index = 0
|
||||
event_background = cwd+"/countdown_event.bmp"
|
||||
big_font = bitmap_font.load_font(cwd+"/fonts/Helvetica-Bold-36.bdf")
|
||||
big_font.load_glyphs(b'0123456789') # pre-load glyphs for fast printing
|
||||
|
||||
backgrounds = ['background_1.bmp',
|
||||
'background_2.bmp',
|
||||
'background_3.bmp',
|
||||
'background_4.bmp',
|
||||
'background_5.bmp',
|
||||
'background_6.bmp',
|
||||
'background_7.bmp',
|
||||
'background_8.bmp',
|
||||
'background_9.bmp',
|
||||
'background_10.bmp',
|
||||
'background_11.bmp',
|
||||
'background_12.bmp',
|
||||
'background_13.bmp',
|
||||
'background_14.bmp',
|
||||
'background_15.bmp',
|
||||
'background_16.bmp',
|
||||
'background_17.bmp',
|
||||
'background_18.bmp']
|
||||
|
||||
background_index = -1
|
||||
event_background = cwd+"/happy_halloween.bmp"
|
||||
|
||||
days_position = (8, 207)
|
||||
hours_position = (110, 207)
|
||||
minutes_position = (220, 207)
|
||||
text_color = 0xFFFFFF
|
||||
|
||||
# Initialize the pyportal object and let us know what data to fetch and where
|
||||
# to display it
|
||||
pyportal = PyPortal(status_neopixel=board.NEOPIXEL,
|
||||
default_bg=cwd + backgrounds[0],
|
||||
external_spi=spi,
|
||||
esp=esp,
|
||||
caption_text='Days Remaining',
|
||||
caption_font=cwd+'/fonts/Helvetica-Bold-36.bdf',
|
||||
caption_position=(13, 160))
|
||||
caption_position=(13, 215),
|
||||
caption_color=0x000000)
|
||||
|
||||
# Create the count label
|
||||
big_font = bitmap_font.load_font(cwd+"/fonts/Helvetica-Bold-36.bdf")
|
||||
big_font.load_glyphs(b'0123456789') # pre-load glyphs for fast printing
|
||||
text_color = 0xFFFFFF
|
||||
countdown_text = Label(big_font, max_glyphs=3)
|
||||
countdown_text.x = 120
|
||||
countdown_text.y = 100
|
||||
countdown_text.color = text_color
|
||||
countdown_text.x = 130
|
||||
countdown_text.y = 20
|
||||
countdown_text.color = 0x000000
|
||||
pyportal.splash.append(countdown_text)
|
||||
|
||||
refresh_time = None
|
||||
|
||||
connect_esp()
|
||||
bearer_token = get_bearer_token()
|
||||
|
||||
headers = {'Authorization': 'Bearer ' + bearer_token}
|
||||
|
||||
while True:
|
||||
# only query the online time once per hour (and on first run)
|
||||
if (not refresh_time) or (time.monotonic() - refresh_time) > 3600:
|
||||
try:
|
||||
logger.debug("Getting tweet from internet!")
|
||||
print("Getting time from internet!")
|
||||
pyportal.get_local_time(location=secrets['timezone'])
|
||||
refresh_time = time.monotonic()
|
||||
except RuntimeError as e:
|
||||
logger.error("Some error occured, retrying! -", e)
|
||||
print("Some error occured, retrying! -", e)
|
||||
continue
|
||||
|
||||
#fetch the tweetstream from HalloweenCounts
|
||||
response = requests.get('https://api.twitter.com/1.1/statuses/user_timeline.json?count=1&screen_name=halloweencounts',
|
||||
headers=headers)
|
||||
if response.status_code != 200:
|
||||
logger.error('Tweet fetch status: %d', response.status_code)
|
||||
else:
|
||||
timeline = json.loads(response.content)
|
||||
latest_tweet = timeline[0]
|
||||
tweet_text = latest_tweet['text']
|
||||
days_remaining = int(tweet_text.split(' Days Until')[0].split(' ')[-1])
|
||||
logger.debug('Days remaining: %d', days_remaining)
|
||||
#if it's halloween, set the bground and stop
|
||||
if days_remaining == 0:
|
||||
pyportal.set_background(event_background)
|
||||
while True:
|
||||
pass
|
||||
else:
|
||||
background_index = (background_index + 1) % len(backgrounds)
|
||||
pyportal.set_background(backgrounds[background_index])
|
||||
countdown_text.text = '{:>3}'.format(days_remaining)
|
||||
timestamp = time.localtime()
|
||||
now = time.struct_time((timestamp[0], timestamp[1], timestamp[2],
|
||||
0, 0, 0, # we don't track seconds
|
||||
-1, -1, False)) # we dont know day of week/year or DST
|
||||
|
||||
# check every minute
|
||||
print("Current time:", now)
|
||||
remaining = time.mktime(event_time) - time.mktime(now)
|
||||
print("Time remaining (s):", remaining)
|
||||
if remaining < 0:
|
||||
# oh, its event time!
|
||||
pyportal.set_background(event_background)
|
||||
while True: # that's all folks
|
||||
pass
|
||||
secs_remaining = remaining % 60
|
||||
remaining //= 60
|
||||
mins_remaining = remaining % 60
|
||||
remaining //= 60
|
||||
hours_remaining = remaining % 24
|
||||
remaining //= 24
|
||||
days_remaining = remaining
|
||||
print("%d days, %d hours, %d minutes and %s seconds" %
|
||||
(days_remaining, hours_remaining, mins_remaining, secs_remaining))
|
||||
countdown_text.text = '{:>3}'.format(days_remaining)
|
||||
pyportal.set_background(backgrounds[random.randint(0, 17)])
|
||||
|
||||
# update every minute
|
||||
time.sleep(60)
|
||||
|
|
|
|||
BIN
PyPortal_Halloween_Countdown/happy_halloween.bmp
Normal file
|
After Width: | Height: | Size: 225 KiB |
|
|
@ -4,6 +4,7 @@
|
|||
secrets = {
|
||||
'ssid' : 'CHANGE ME',
|
||||
'password' : 'CHANGE ME',
|
||||
'twitter_api_key': 'CHANGE ME',
|
||||
'twitter_secret_key': 'CHANGE ME'
|
||||
'timezone' : 'CHANGE ME',
|
||||
'aio_username' : 'CHANGE ME',
|
||||
'aio_key' : 'CHANGE ME',
|
||||
}
|
||||
|
|
|
|||