Last active
February 5, 2025 01:23
-
-
Save dnnsmnstrr/0e2d19b381b3fc6174a049118f33718a to your computer and use it in GitHub Desktop.
Display informantion on the EnkPi 4.2in E-Paper Display
This file contains hidden or 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 network | |
import ntptime | |
import utime | |
import math | |
import urequests | |
import EnkPi_4in2 as epd | |
from EnkPi_4in2 import Buzzer | |
from main import sync_time | |
from machine import Pin | |
from secrets import SSID, PASSWORD, WEATHER_API_KEY # Add your weather API key here | |
from font import font, draw_custom_text | |
# Configuration | |
CITY = "Mainz" # Update with your desired city | |
Led = Pin("LED", Pin.OUT) | |
e_paper = epd.E_paper() | |
button1 = Pin(2, Pin.IN, Pin.PULL_UP) | |
button2 = Pin(4, Pin.IN, Pin.PULL_UP) | |
button3 = Pin(3, Pin.IN, Pin.PULL_UP) | |
button4 = Pin(5, Pin.IN, Pin.PULL_UP) | |
button5 = Pin(14, Pin.IN, Pin.PULL_UP) | |
button6 = Pin(15, Pin.IN, Pin.PULL_UP) | |
def connect_wifi(): | |
wlan = network.WLAN(network.STA_IF) | |
wlan.active(True) | |
wlan.connect(SSID, PASSWORD) | |
print("Connecting to Wi-Fi...") | |
while not wlan.isconnected(): | |
utime.sleep(1) | |
print("Connected:", wlan.ifconfig()) | |
def fetch_api_text(url, key="text"): | |
"""Fetches text from an API, parses JSON, and extracts the 'text' key""" | |
try: | |
response = urequests.get(url) | |
if response.status_code == 200: | |
try: | |
data = response.json() | |
print("API Response: " + response.text) | |
return data.get(key, "No Text") | |
except ValueError: | |
print("Invalid JSON response") | |
return "Invalid JSON" | |
else: | |
print(f"Error: {response.status_code}") | |
return "Error" | |
except Exception as e: | |
print("API Request Failed:", str(e)) | |
return "No Data" | |
def fetch_weather(city=CITY): | |
""" | |
Fetches weather data from OpenWeatherMap and returns the temperature in °C and weather description. | |
""" | |
url = "https://api.openweathermap.org/data/2.5/weather?q={},DE&appid={}&units=metric".format(city, WEATHER_API_KEY) | |
try: | |
response = urequests.get(url) | |
if response.status_code == 200: | |
data = response.json() | |
temp = data.get("main", {}).get("temp", "N/A") | |
print("Weather in ", city, ": ", temp, "°C,") | |
return temp | |
else: | |
print("Weather API Error:", response.status_code) | |
return "Err", "Error" | |
except Exception as e: | |
print("Weather fetch failed:", e) | |
return "Err", "Error" | |
def sync_time(): | |
print("Syncing time with NTP...") | |
ntptime.settime() | |
print("Time synced!") | |
def get_current_hour(): | |
# Get current time in seconds since the Epoch (1/1/2000) | |
current_time = utime.time() | |
# Convert to seconds (divide by 1000) | |
current_time_sec = current_time // 1000 | |
# Get the current hour (0-23) | |
current_hour = (current_time_sec // 3600) % 24 | |
return current_hour | |
def get_formatted_datetime(): | |
""" | |
Gets local time and formats it as DD.MM.YYYY and HH:MM. | |
Adjusts UTC to CET (UTC+1). | |
""" | |
offsetHours = 1 | |
local_time = utime.localtime(utime.time() + (offsetHours * 3600)) | |
date_str = "{:02}.{:02}".format(local_time[2], local_time[1]) | |
year_str = "{:04}".format(local_time[0]) | |
time_str = "{:02}:{:02}".format(local_time[3], local_time[4]) | |
return date_str, time_str, year_str | |
def get_day_of_week(offset_hours=1): | |
""" | |
Returns the day of the week as a string. | |
The utime.localtime() function returns a tuple: | |
(year, month, mday, hour, minute, second, weekday, yearday) | |
where weekday is 0 for Monday and 6 for Sunday. | |
The offset_hours parameter lets you adjust the UTC time (e.g., UTC+1 for CET). | |
""" | |
# List of days starting with Monday (index 0) to Sunday (index 6) | |
day_names = ["Mon.", "Tue.", "Wed.", "Thu.", "Fri.", "Sat.", "Sun."] | |
# Get the local time adjusted by offset_hours (e.g., for CET, offset_hours=1) | |
current_time = utime.time() + (offset_hours * 3600) | |
local_time = utime.localtime(current_time) | |
# local_time[6] gives the weekday (0=Monday, ..., 6=Sunday) | |
return day_names[local_time[6]].upper() | |
def display_text(text, x=10, y=10, max_chars_per_line=45): | |
"""Displays multiline text on the e-paper display.""" | |
lines = [text[i:i+max_chars_per_line] for i in range(0, len(text), max_chars_per_line)] | |
for line in lines: | |
e_paper.imageblack.text(line.strip(), x, y, 0x00) | |
y += 20 | |
def ring(x,y,r,c): | |
e_paper.imageblack.pixel(x-r,y,c) | |
e_paper.imageblack.pixel(x+r,y,c) | |
e_paper.imageblack.pixel(x,y-r,c) | |
e_paper.imageblack.pixel(x,y+r,c) | |
for i in range(1,r): | |
a = int(math.sqrt(r*r-i*i)) | |
e_paper.imageblack.pixel(x-a,y-i,c) | |
e_paper.imageblack.pixel(x+a,y-i,c) | |
e_paper.imageblack.pixel(x-a,y+i,c) | |
e_paper.imageblack.pixel(x+a,y+i,c) | |
e_paper.imageblack.pixel(x-i,y-a,c) | |
e_paper.imageblack.pixel(x+i,y-a,c) | |
e_paper.imageblack.pixel(x-i,y+a,c) | |
e_paper.imageblack.pixel(x+i,y+a,c) | |
def circle(x,y,r,c): | |
e_paper.imageblack.hline(x-r,y,r*2,c) | |
for i in range(1,r): | |
a = int(math.sqrt(r*r-i*i)) # Pythagoras! | |
e_paper.imageblack.hline(x-a,y+i,a*2,c) # Lower half | |
e_paper.imageblack.hline(x-a,y-i,a*2,c) # Upper half | |
def draw_ui_elements(offset = 70, width = 400, padding = 10): | |
#e_paper.imageblack.hline(padding, divider_y, width - (2*padding), 0x00) | |
# Draw rectangle border around the weather container. | |
# Example rectangle parameters: | |
weather_x = padding | |
weather_y = offset | |
weather_width = width - (2*padding) | |
weather_height = 70 | |
# Top horizontal line | |
e_paper.imageblack.hline(weather_x, weather_y, weather_width, 0x00) | |
# Bottom horizontal line | |
e_paper.imageblack.hline(weather_x, weather_y + weather_height, weather_width, 0x00) | |
# Left vertical line | |
e_paper.imageblack.vline(weather_x, weather_y, weather_height, 0x00) | |
# Right vertical line | |
e_paper.imageblack.vline(weather_x + weather_width, weather_y, weather_height, 0x00) | |
# Buttons | |
button_levels = [278, 190, 30] | |
display_text("LED", x=0, y=button_levels[0]) | |
display_text("Refresh", x=340, y=button_levels[0]) | |
def update_display(): | |
print("refreshing display") | |
# Clear the display buffers | |
e_paper.imageblack.fill(0xff) | |
e_paper.imagered.fill(0xff) | |
padding = 10 | |
# Get formatted date and time | |
date_str, time_str, year_str = get_formatted_datetime() | |
day = get_day_of_week() | |
draw_custom_text(e_paper.imageblack, day, 10, 10, scale=7) | |
draw_custom_text(e_paper.imageblack, date_str, 190, 10, scale=7) | |
draw_custom_text(e_paper.imageblack, "Last update {}".format(time_str), 295, 295, scale=1) | |
#draw_custom_text(e_paper.imageblack, time_str, 10, 50, scale=12) | |
# Draw UI elements (divider lines, containers) on the display | |
weather_offset = 60 | |
draw_ui_elements(60) | |
# Fetch and display weather information in the container | |
temp = fetch_weather() | |
celcius = "{} C".format(temp) | |
scale = 8 | |
draw_custom_text(e_paper.imageblack, celcius, 30, weather_offset + padding * 2, scale) | |
degX = (len(celcius) - 1) * 6 * scale + 10 | |
degY = weather_offset + padding + 5 | |
circle(degX,degY,8,0x00) | |
circle(degX,degY,4,0xFF) | |
display_text(CITY, x=18, y=weather_offset + 5) | |
api_url = "https://thefact.space/random" | |
text = fetch_api_text(api_url) | |
display_text(text, padding, (weather_offset + padding * 2) * 2) | |
# Update the e-paper display with both black and red buffers | |
e_paper.display(e_paper.buffer_black, e_paper.buffer_red) | |
def main_loop(): | |
update_interval = 15 * 60000 # Update every 60.000 ms (1 minute) | |
night_update_interval = 60 * 60000 # 60 minutes | |
beep_duration = 0.1 | |
break_duration = 0.3 | |
DEBOUNCE_DELAY = 50 | |
last_display_update = utime.ticks_ms() | |
last_button_press_time = 0 | |
led_on = False | |
button_last_state = { | |
'button1': 1, | |
'button2': 1, | |
'button3': 1, | |
'button4': 1, | |
'button5': 1, | |
'button6': 1 | |
} | |
while True: | |
current_time = utime.ticks_ms() | |
current_hour = get_current_hour() | |
current_update_interval = update_interval if 6 <= current_hour <= 24 else night_update_interval | |
# Button Handling | |
for button_name in ['button1', 'button2', 'button3', 'button4', 'button5', 'button6']: | |
button = globals()[button_name] | |
current_state = button.value() | |
if current_state == 0: # Button is pressed | |
if button_last_state[button_name] == 1: # Transition from released to pressed | |
if utime.ticks_diff(current_time, last_button_press_time) > DEBOUNCE_DELAY: | |
last_button_press_time = current_time | |
print(button_name, " pressed.") | |
if button_name == 'button1': | |
print("LED is ", "on," if led_on else "off,", "toggling") | |
led_on = not led_on | |
Led.on() if led_on else Led.off() | |
elif button_name == 'button6': | |
sync_time() | |
update_display() | |
else: | |
Buzzer.tone(587, beep_duration, break_duration) | |
button_last_state[button_name] = 0 | |
else: # Button is released | |
button_last_state[button_name] = 1 | |
# Check if it is time to refresh display | |
if utime.ticks_diff(current_time, last_display_update) >= current_update_interval: | |
update_display() | |
last_display_update = current_time | |
utime.sleep_ms(20) | |
try: | |
connect_wifi() | |
sync_time() | |
update_display() | |
main_loop() | |
except KeyboardInterrupt: | |
e_paper.sleep() | |
print("Display update stopped.") |
This file contains hidden or 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
# Extended bitmap font for digits, special characters, and the alphabet A-Z | |
font = { | |
# Digits | |
"0": [ | |
" XXX ", | |
"X X", | |
"X X", | |
"X X", | |
" XXX " | |
], | |
"1": [ | |
" X ", | |
" XX ", | |
" X ", | |
" X ", | |
" XXX " | |
], | |
"2": [ | |
" XXX ", | |
"X X", | |
" X ", | |
" X ", | |
"XXXXX" | |
], | |
"3": [ | |
" XXX ", | |
"X X", | |
" X ", | |
"X X", | |
" XXX " | |
], | |
"4": [ | |
"X X", | |
"X X", | |
"XXXXX", | |
" X", | |
" X" | |
], | |
"5": [ | |
"XXXXX", | |
"X ", | |
"XXXX ", | |
" X", | |
"XXXX " | |
], | |
"6": [ | |
" XXX ", | |
"X ", | |
"XXXX ", | |
"X X", | |
" XXX " | |
], | |
"7": [ | |
"XXXXX", | |
" X", | |
" X ", | |
" X ", | |
" X " | |
], | |
"8": [ | |
" XXX ", | |
"X X", | |
" XXX ", | |
"X X", | |
" XXX " | |
], | |
"9": [ | |
" XXX ", | |
"X X", | |
" XXXX", | |
" X", | |
" XXX " | |
], | |
# Alphabet A-Z (Capital letters) | |
"A": [ | |
" X ", | |
" X X ", | |
"XXXXX", | |
"X X", | |
"X X" | |
], | |
"B": [ | |
"XXXX ", | |
"X X", | |
"XXXX ", | |
"X X", | |
"XXXX " | |
], | |
"C": [ | |
" XXX ", | |
"X ", | |
"X ", | |
"X ", | |
" XXX " | |
], | |
"D": [ | |
"XXXX ", | |
"X X", | |
"X X", | |
"X X", | |
"XXXX " | |
], | |
"E": [ | |
"XXXXX", | |
"X ", | |
"XXX ", | |
"X ", | |
"XXXXX" | |
], | |
"F": [ | |
"XXXXX", | |
"X ", | |
"XXX ", | |
"X ", | |
"X " | |
], | |
"G": [ | |
" XXX ", | |
"X ", | |
"X XXX", | |
"X X", | |
" XXX " | |
], | |
"H": [ | |
"X X", | |
"X X", | |
"XXXXX", | |
"X X", | |
"X X" | |
], | |
"I": [ | |
" XXX ", | |
" X ", | |
" X ", | |
" X ", | |
" XXX " | |
], | |
"J": [ | |
" XXX", | |
" X ", | |
" X ", | |
"X X ", | |
" XX " | |
], | |
"K": [ | |
"X X", | |
"X X ", | |
"XXX ", | |
"X X ", | |
"X X" | |
], | |
"L": [ | |
"X ", | |
"X ", | |
"X ", | |
"X ", | |
"XXXXX" | |
], | |
"M": [ | |
"X X", | |
"XX XX", | |
"X X X", | |
"X X", | |
"X X" | |
], | |
"N": [ | |
"X X", | |
"XX X", | |
"X X X", | |
"X XX", | |
"X X" | |
], | |
"O": [ | |
" XXX ", | |
"X X", | |
"X X", | |
"X X", | |
" XXX " | |
], | |
"P": [ | |
"XXXX ", | |
"X X", | |
"XXXX ", | |
"X ", | |
"X " | |
], | |
"Q": [ | |
" XXX ", | |
"X X", | |
"X X", | |
"X XX", | |
" XXXX" | |
], | |
"R": [ | |
"XXXX ", | |
"X X", | |
"XXXX ", | |
"X X ", | |
"X X" | |
], | |
"S": [ | |
" XXXX", | |
"X ", | |
" XXX ", | |
" X", | |
"XXXX " | |
], | |
"T": [ | |
"XXXXX", | |
" X ", | |
" X ", | |
" X ", | |
" X " | |
], | |
"U": [ | |
"X X", | |
"X X", | |
"X X", | |
"X X", | |
" XXX " | |
], | |
"V": [ | |
"X X", | |
"X X", | |
"X X", | |
" X X ", | |
" X " | |
], | |
"W": [ | |
"X X", | |
"X X", | |
"X X X", | |
"XX XX", | |
"X X" | |
], | |
"X": [ | |
"X X", | |
" X X ", | |
" X ", | |
" X X ", | |
"X X" | |
], | |
"Y": [ | |
"X X", | |
" X X ", | |
" X ", | |
" X ", | |
" X " | |
], | |
"Z": [ | |
"XXXXX", | |
" X ", | |
" X ", | |
" X ", | |
"XXXXX" | |
], | |
# special characters | |
":": [ | |
" ", | |
" X ", | |
" ", | |
" X ", | |
" " | |
], | |
".": [ | |
" ", | |
" ", | |
" ", | |
" ", | |
" X " | |
], | |
",": [ | |
" ", | |
" ", | |
" ", | |
" X ", | |
" X " | |
], | |
"-": [ | |
" ", | |
" ", | |
" XXX ", | |
" ", | |
" " | |
], | |
"/": [ | |
" x", | |
" x ", | |
" x ", | |
" x ", | |
"x " | |
], | |
"|": [ | |
" X ", | |
" X ", | |
" X ", | |
" X ", | |
" X " | |
] | |
} | |
def draw_custom_text(frame, text, x, y, scale=1, color=0): | |
""" | |
Draw text using the custom extended_font. | |
For each character, the corresponding bitmap is rendered by drawing filled rectangles. | |
frame: a framebuf.FrameBuffer instance | |
text: string to render | |
x, y: starting coordinates | |
scale: factor to scale each pixel (1 means each pixel is rendered as a 1x1 block) | |
color: color to use (e.g. 0x00 for black) | |
""" | |
for char in text: | |
# Get bitmap for the character; skip unknown characters | |
bitmap = font.get(char.upper()) | |
if bitmap: | |
for row, line in enumerate(bitmap): | |
for col, pixel in enumerate(line): | |
if pixel == "X": | |
frame.fill_rect(x + col * scale, y + row * scale, scale, scale, color) | |
x += (len(bitmap[0]) + 1) * scale # Move x-position for next char, add a column as spacing | |
else: | |
# If character not found in font dictionary, add blank space | |
x += (5 + 1) * scale | |
This file contains hidden or 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 network | |
import utime | |
import ntptime | |
from machine import Pin | |
from secrets import SSID, PASSWORD | |
# Initialize WLAN | |
wlan = network.WLAN(network.STA_IF) | |
wlan.active(True) | |
def connect_to_wifi(): | |
# Connect to your network | |
print(f"Connecting to {SSID}...") | |
wlan.connect(SSID, PASSWORD) | |
# Wait for connection | |
max_retries = 10 | |
for i in range(max_retries): | |
if wlan.isconnected(): | |
print("Connected!") | |
print(f"IP Address: {wlan.ifconfig()[0]}") | |
return True | |
utime.sleep_ms(500) | |
print(f"Retrying connection... ({i+1}/{max_retries})") | |
print("Failed to connect to Wi-Fi.") | |
def sync_time(): | |
# Sync time with NTP server | |
print("Synchronizing time...") | |
try: | |
ntptime.settime() | |
print("Time synced successfully!") | |
return True | |
except Exception as e: | |
print(f"Failed to sync time: {e}") | |
return False | |
def blink(): | |
led = Pin("LED", Pin.OUT) | |
led.toggle() | |
utime.sleep(1) | |
led.toggle() | |
utime.sleep(1) | |
led.toggle() | |
utime.sleep(1) | |
led.off() | |
def main(): | |
blink() | |
# Connect to Wi-Fi | |
if connect_to_wifi(): | |
# Sync time after connecting to Wi-Fi | |
if sync_time(): | |
print("Current UTC time:", utime.localtime()) | |
else: | |
print("Time sync failed. Using default time.") | |
else: | |
print("Wi-Fi connection failed. Cannot sync time.") | |
try: | |
import enkpi | |
except Exception as e: | |
print(f"Error in main.py: {e}") | |
utime.sleep(1) | |
# Run on boot | |
main() |
This file contains hidden or 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
SSID = "" | |
PASSWORD = "" | |
WEATHER_API_KEY = "" # from https://home.openweathermap.org/api_keys |
Features
- display date
- weather for defined city
- random fact
- toggle LED
- button beep
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Software
https://github.com/sbcshop/EnkPi_4.2_Software
Hardware
https://github.com/sbcshop/EnkPi_4.2_Hardware
Musical tones (in Hz)
do5 = 523
dod5 = 554
re5 = 587
red5 = 622
mi5 = 659
fa5 = 698
fad5 = 739
sol5 = 784
sold5 = 830
la5 = 880
lad5 = 932
si5 = 987