Last active
October 2, 2024 15:40
-
-
Save jepler/b2c020a6caa65a31297053b7216fcc15 to your computer and use it in GitHub Desktop.
CircuitPython code for https://adafruit-playground.com/u/jepler/pages/e-ink-countdown-in-circuitpython-with-custom-stand
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import math | |
import sys | |
import time | |
import alarm | |
import board | |
import displayio | |
import socketpool | |
import rtc | |
import wifi | |
from fourwire import FourWire | |
import adafruit_datetime | |
import adafruit_ntp | |
import adafruit_ssd1680 | |
import adafruit_lc709203f | |
from font_orbitron_medium_webfont_54_latin1 import FONT | |
from adafruit_display_text.bitmap_label import Label | |
def connect_wifi(): | |
if wifi.radio.ipv4_address is None: | |
print("connect wifi") | |
print(os.getenv('WIFI_SSID'), os.getenv('WIFI_PASSWORD')) | |
for _ in range(5): | |
try: | |
wifi.radio.connect(os.getenv('WIFI_SSID'), os.getenv('WIFI_PASSWORD')) | |
return | |
except Exception as e: | |
print(e) | |
# This pinout works on a Metro M4 and may need to be altered for other boards. | |
spi = board.SPI() # Uses SCK and MOSI | |
epd_cs = board.D9 | |
epd_dc = board.D10 | |
epd_reset = None | |
epd_busy = None | |
def setup_display(): | |
global display, group | |
displayio.release_displays() | |
display_bus = FourWire( | |
spi, command=epd_dc, chip_select=epd_cs, reset=epd_reset, baudrate=1000000 | |
) | |
time.sleep(1) | |
# For issues with display not updating top/bottom rows correctly set colstart to 8 | |
display = adafruit_ssd1680.SSD1680( | |
display_bus, | |
height=250, | |
width=122, | |
busy_pin=epd_busy, | |
highlight_color=0x000000, | |
colstart=0, | |
rotation=180, | |
black_bits_inverted=1 | |
) | |
group = displayio.Group() | |
display.root_group = group | |
def main(): | |
battery_monitor = adafruit_lc709203f.LC709203F(board.I2C()) | |
for _ in range(5): | |
try: | |
percent = battery_monitor.cell_percent | |
break | |
except Exception as e: | |
print(e) | |
else: | |
print("too much battery polling") | |
percent = 100 | |
if percent < 5: return 86400 | |
target = adafruit_datetime.datetime(2025, 3, 28, 21) # UTC, 4PM local time | |
sm0 = alarm.sleep_memory[0] | |
if sm0 == 0: | |
# Assumes wifi connected via settings.toml before code.py started! | |
connect_wifi() | |
pool = socketpool.SocketPool(wifi.radio) | |
ntp = adafruit_ntp.NTP(pool, tz_offset=0, cache_seconds=3600, socket_timeout=1) | |
for _ in range(10): | |
try: | |
rtc.RTC().datetime = ntp.datetime | |
alarm.sleep_memory[0] = 24 # Don't set again for about 24h | |
break | |
except Exception as e: | |
print(e) | |
else: | |
print("NTP too much") | |
# Hopefully it's better in a few hundred seconds | |
return 300 | |
else: | |
print(f"Don't need to set for {sm0} hours") | |
alarm.sleep_memory[0] = sm0-1 | |
labels = [ | |
Label( | |
FONT, | |
text="", | |
anchor_point=(0.5, 0.5), | |
anchored_position=(display.width // 2, display.height * (i + 0.5) // 3), | |
) | |
for i in range(4) | |
] | |
for l in labels: | |
group.append(l) | |
for _ in range(10): | |
try: | |
now = adafruit_datetime.datetime.now() | |
break | |
except Exception as e: | |
print(e) | |
else: | |
print("NTP too much") | |
# Hopefully it's better in a few hundred seconds | |
return 300 | |
delta = target - now | |
delta = adafruit_datetime.timedelta( | |
seconds=math.ceil(delta.total_seconds() / 3600) * 3600 | |
) | |
weeks = delta.days // 7 | |
days = delta.days % 7 | |
hours = delta.seconds // 3600 | |
low_bat = percent < 25 | |
if delta.total_seconds() < 0: | |
labels[0].text = "GO" | |
labels[1].text = "GO" | |
labels[2].text = "GO" | |
else: | |
labels[0].text = f"{weeks}" | |
labels[1].text = f"{days}" | |
labels[2].text = "bat!" if low_bat else f"{hours}" | |
t0 = time.time() | |
display.refresh() | |
t1 = time.time() | |
if delta.total_seconds() < 0: | |
return sys.maxsize | |
if low_bat: | |
# Sleep until tomorrow to preserve the battery... | |
sleep_until = (now + adafruit_datetime.timedelta(seconds=86400)).replace( | |
hour=6, minute=0, second=0 | |
) | |
else: | |
# Sleep a bit into the next hour, it's just fine to be 90 seconds late, | |
# but avoid waking if somehow we get 90 seconds of clock skew within | |
# the next hour... | |
sleep_until = (now + adafruit_datetime.timedelta(seconds=3600)).replace( | |
minute=1, second=30 | |
) | |
return (sleep_until - now).total_seconds() | |
if __name__ == "__main__": | |
to_sleep = 300 | |
try: | |
setup_display() | |
to_sleep = main() | |
finally: | |
displayio.release_displays() | |
print(f"sleeping for {to_sleep} seconds") | |
alarm.exit_and_deep_sleep_until_alarms( | |
alarm.time.TimeAlarm(monotonic_time=time.monotonic() + to_sleep) | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment