Skip to content

Commit 75b7348

Browse files
authored
Merge pull request #2867 from tyeth/circuitpython_day_countdown_io_timeservice
Circuitpython day countdown using Adafruit IO timeservice
2 parents 2b88f8d + fd2baf3 commit 75b7348

File tree

6 files changed

+279
-0
lines changed

6 files changed

+279
-0
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries
2+
# SPDX-FileCopyrightText: 2024 Tyeth Gundry for Adafruit Industries
3+
#
4+
# SPDX-License-Identifier: MIT
5+
6+
import os
7+
import time
8+
import wifi
9+
import board
10+
import displayio
11+
import microcontroller
12+
import adafruit_connection_manager
13+
import adafruit_requests
14+
from adafruit_io.adafruit_io import IO_HTTP
15+
from adafruit_bitmap_font import bitmap_font
16+
from adafruit_display_text import bitmap_label
17+
from adafruit_ticks import ticks_ms, ticks_add, ticks_diff
18+
19+
## See TZ Identifier column at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
20+
## If you want to set the timezone, you can do so with the following code, which
21+
## attempts to get timezone from settings.toml or defaults to New York
22+
timezone = os.getenv("ADAFRUIT_AIO_TIMEZONE", "America/New_York")
23+
## Or instead rely on automatic timezone detection based on IP Address
24+
# timezone = None
25+
26+
27+
## The time of the thing!
28+
EVENT_YEAR = 2024
29+
EVENT_MONTH = 8
30+
EVENT_DAY = 16
31+
EVENT_HOUR = 0
32+
EVENT_MINUTE = 0
33+
## we'll make a python-friendly structure
34+
event_time = time.struct_time(
35+
(
36+
EVENT_YEAR,
37+
EVENT_MONTH,
38+
EVENT_DAY,
39+
EVENT_HOUR,
40+
EVENT_MINUTE,
41+
0, # we don't track seconds
42+
-1, # we dont know day of week/year or DST
43+
-1,
44+
False,
45+
)
46+
)
47+
48+
print("Connecting to WiFi...")
49+
wifi.radio.connect(
50+
os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")
51+
)
52+
53+
## Initialize a requests session using the newer connection manager
54+
## See https://adafruit-playground.com/u/justmobilize/pages/adafruit-connection-manager
55+
pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio)
56+
ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio)
57+
requests = adafruit_requests.Session(pool, ssl_context)
58+
59+
## Create an instance of the Adafruit IO HTTP client
60+
io = IO_HTTP(
61+
os.getenv("ADAFRUIT_AIO_USERNAME"), os.getenv("ADAFRUIT_AIO_KEY"), requests
62+
)
63+
64+
## Setup display and size appropriate assets
65+
if board.board_id == "adafruit_qualia_s3_rgb666":
66+
# Display Initialisation for 3.2" Bar display (320x820)
67+
from qualia_bar_display_320x820 import setup_display
68+
display = setup_display()
69+
display.rotation = 90 # Rotate the display
70+
BITMAP_FILE = "/circuitpython_day_2024_820x260_16bit.bmp"
71+
FONT_FILE = "/font_free_mono_bold_48.pcf"
72+
FONT_Y_OFFSET = 30
73+
blinka_bitmap = displayio.OnDiskBitmap(BITMAP_FILE)
74+
PIXEL_SHADER = displayio.ColorConverter(
75+
input_colorspace=displayio.Colorspace.RGB565
76+
)
77+
else:
78+
# Setup built-in display
79+
display = board.DISPLAY
80+
BITMAP_FILE = "/cpday_tft.bmp"
81+
FONT_FILE = "/Helvetica-Bold-16.pcf"
82+
FONT_Y_OFFSET = 13
83+
PIXEL_SHADER = displayio.ColorConverter()
84+
blinka_bitmap = displayio.OnDiskBitmap(BITMAP_FILE)
85+
PIXEL_SHADER = blinka_bitmap.pixel_shader
86+
group = displayio.Group()
87+
font = bitmap_font.load_font(FONT_FILE)
88+
blinka_grid = displayio.TileGrid(blinka_bitmap, pixel_shader=blinka_bitmap.pixel_shader)
89+
scrolling_label = bitmap_label.Label(font, text=" ", y=display.height - FONT_Y_OFFSET)
90+
91+
group.append(blinka_grid)
92+
group.append(scrolling_label)
93+
display.root_group = group
94+
display.auto_refresh = False
95+
96+
refresh_clock = ticks_ms()
97+
refresh_timer = 3600 * 1000 # 1 hour
98+
clock_clock = ticks_ms()
99+
clock_timer = 1000
100+
scroll_clock = ticks_ms()
101+
scroll_timer = 50
102+
first_run = True
103+
finished = False
104+
triggered = False
105+
106+
while True:
107+
# only query the online time once per hour (and on first run)
108+
if ticks_diff(ticks_ms(), refresh_clock) >= refresh_timer or first_run:
109+
try:
110+
print("Getting time from internet!")
111+
now = time.struct_time(io.receive_time(timezone))
112+
print(now)
113+
total_seconds = time.mktime(now)
114+
refresh_clock = ticks_add(refresh_clock, refresh_timer)
115+
except Exception as e: # pylint: disable=broad-except
116+
print("Some error occured, retrying via reset in 15seconds! -", e)
117+
time.sleep(15)
118+
microcontroller.reset()
119+
120+
if ticks_diff(ticks_ms(), clock_clock) >= clock_timer:
121+
remaining = time.mktime(event_time) - total_seconds
122+
if remaining < 0:
123+
# calculate time since event
124+
remaining = abs(remaining)
125+
secs_remaining = -(remaining % 60)
126+
remaining //= 60
127+
mins_remaining = -(remaining % 60)
128+
remaining //= 60
129+
hours_remaining = -(remaining % 24)
130+
remaining //= 24
131+
days_remaining = -remaining
132+
finished = True
133+
if not first_run and days_remaining == 0:
134+
scrolling_label.text = (
135+
"It's CircuitPython Day 2024! The snakiest day of the year!"
136+
)
137+
138+
# Check for the moment of the event to trigger something (a NASA snake launch)
139+
if not triggered and (
140+
hours_remaining == 0
141+
and mins_remaining == 0
142+
and secs_remaining <= 1
143+
# Change at/after xx:yy:01 seconds so we've already updated the display
144+
):
145+
# send a signal to an adafruit IO feed, where an Action is listening
146+
print("Launch the snakes! (sending message to Adafruit IO)")
147+
triggered = True
148+
io.send_data("cpday-countdown", "Launch the snakes!")
149+
150+
else:
151+
# calculate time until event
152+
secs_remaining = remaining % 60
153+
remaining //= 60
154+
mins_remaining = remaining % 60
155+
remaining //= 60
156+
hours_remaining = remaining % 24
157+
remaining //= 24
158+
days_remaining = remaining
159+
if not finished or (finished and days_remaining < 0):
160+
# Add 1 to negative days_remaining to count from end of day instead of start
161+
if days_remaining < 0:
162+
days_remaining += 1
163+
# Update the display with current countdown value
164+
scrolling_label.text = (
165+
f"{days_remaining} DAYS, {hours_remaining} HOURS,"
166+
+ f"{mins_remaining} MINUTES & {secs_remaining} SECONDS"
167+
)
168+
169+
total_seconds += 1
170+
clock_clock = ticks_add(clock_clock, clock_timer)
171+
if ticks_diff(ticks_ms(), scroll_clock) >= scroll_timer:
172+
scrolling_label.x -= 1
173+
if scrolling_label.x < -(scrolling_label.width + 5):
174+
scrolling_label.x = display.width + 2
175+
display.refresh()
176+
scroll_clock = ticks_add(scroll_clock, scroll_timer)
177+
178+
first_run = False
Binary file not shown.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# SPDX-FileCopyrightText: 2024 Tyeth Gundry for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
# pylint: disable=import-outside-toplevel, line-too-long
6+
def setup_display():
7+
# Code taken from Adafruit Qualia ESP32-S3 for RGB-666 Displays Learn Guide - Rectangle Bar 3.2" display
8+
# https://learn.adafruit.com/adafruit-qualia-esp32-s3-for-rgb666-displays/qualia-rgb666-with-tl032fwv01-3-2-320x820-bar-display
9+
from displayio import release_displays
10+
release_displays()
11+
12+
import busio
13+
import board
14+
import dotclockframebuffer
15+
from framebufferio import FramebufferDisplay
16+
17+
init_sequence_tl032 = bytes((
18+
b'\x11\x80d'
19+
b'\xff\x05w\x01\x00\x00\x13'
20+
b'\xef\x01\x08'
21+
b'\xff\x05w\x01\x00\x00\x10'
22+
b'\xc0\x02\xe5\x02'
23+
b'\xc1\x02\x0c\n'
24+
b'\xc2\x02\x07\x0f'
25+
b'\xc3\x01\x02'
26+
b'\xcc\x01\x10'
27+
b'\xcd\x01\x08'
28+
b'\xb0\x10\x00\x08Q\r\xce\x06\x00\x08\x08\x1d\x02\xd0\x0fo6?'
29+
b'\xb1\x10\x00\x10O\x0c\x11\x05\x00\x07\x07\x1f\x05\xd3\x11n4?'
30+
b'\xff\x05w\x01\x00\x00\x11'
31+
b'\xb0\x01M'
32+
b'\xb1\x01\x1c'
33+
b'\xb2\x01\x87'
34+
b'\xb3\x01\x80'
35+
b'\xb5\x01G'
36+
b'\xb7\x01\x85'
37+
b'\xb8\x01!'
38+
b'\xb9\x01\x10'
39+
b'\xc1\x01x'
40+
b'\xc2\x01x'
41+
b'\xd0\x81\x88d'
42+
b'\xe0\x03\x80\x00\x02'
43+
b'\xe1\x0b\x04\xa0\x00\x00\x05\xa0\x00\x00\x00``'
44+
b'\xe2\r00``<\xa0\x00\x00=\xa0\x00\x00\x00'
45+
b'\xe3\x04\x00\x0033'
46+
b'\xe4\x02DD'
47+
b'\xe5\x10\x06>\xa0\xa0\x08@\xa0\xa0\nB\xa0\xa0\x0cD\xa0\xa0'
48+
b'\xe6\x04\x00\x0033'
49+
b'\xe7\x02DD'
50+
b'\xe8\x10\x07?\xa0\xa0\tA\xa0\xa0\x0bC\xa0\xa0\rE\xa0\xa0'
51+
b'\xeb\x07\x00\x01NN\xeeD\x00'
52+
b"\xed\x10\xff\xff\x04Vr\xff\xff\xff\xff\xff\xff'e@\xff\xff"
53+
b'\xef\x06\x10\r\x04\x08?\x1f'
54+
b'\xff\x05w\x01\x00\x00\x13'
55+
b'\xe8\x02\x00\x0e'
56+
b'\xff\x05w\x01\x00\x00\x00'
57+
b'\x11\x80x'
58+
b'\xff\x05w\x01\x00\x00\x13'
59+
b'\xe8\x82\x00\x0c\n'
60+
b'\xe8\x02\x00\x00'
61+
b'\xff\x05w\x01\x00\x00\x00'
62+
b'6\x01\x00'
63+
b':\x01f'
64+
b'\x11\x80x'
65+
b')\x80x'
66+
))
67+
68+
board.I2C().deinit()
69+
i2c = busio.I2C(board.SCL, board.SDA, frequency=400_000)
70+
tft_io_expander = dict(board.TFT_IO_EXPANDER)
71+
#tft_io_expander['i2c_address'] = 0x38 # uncomment for rev B
72+
dotclockframebuffer.ioexpander_send_init_sequence(i2c, init_sequence_tl032, **tft_io_expander)
73+
i2c.deinit()
74+
75+
tft_pins = dict(board.TFT_PINS)
76+
77+
tft_timings = {
78+
"frequency": 16000000,
79+
"width": 320,
80+
"height": 820,
81+
82+
"hsync_pulse_width": 3,
83+
"hsync_back_porch": 251,
84+
"hsync_front_porch": 150,
85+
"hsync_idle_low": False,
86+
87+
"vsync_pulse_width": 6,
88+
"vsync_back_porch": 90,
89+
"vsync_front_porch": 100,
90+
"vsync_idle_low": False,
91+
92+
"pclk_active_high": False,
93+
"pclk_idle_high": False,
94+
"de_idle_high": False,
95+
}
96+
97+
#bitmap = displayio.OnDiskBitmap("/display-ruler-720p.bmp")
98+
99+
fb = dotclockframebuffer.DotClockFramebuffer(**tft_pins, **tft_timings)
100+
display = FramebufferDisplay(fb, auto_refresh=False)
101+
return display

0 commit comments

Comments
 (0)