Non twitter version with images

This commit is contained in:
Dave Astels 2019-07-10 17:20:49 -04:00
parent 17b499cac7
commit 031e70be99
22 changed files with 78 additions and 218 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

View file

@ -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')

View file

@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

View file

@ -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',
}