Skip to content

Instantly share code, notes, and snippets.

@dnnsmnstrr
Last active February 5, 2025 01:23
Show Gist options
  • Save dnnsmnstrr/0e2d19b381b3fc6174a049118f33718a to your computer and use it in GitHub Desktop.
Save dnnsmnstrr/0e2d19b381b3fc6174a049118f33718a to your computer and use it in GitHub Desktop.
Display informantion on the EnkPi 4.2in E-Paper Display
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.")
# 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
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()
SSID = ""
PASSWORD = ""
WEATHER_API_KEY = "" # from https://home.openweathermap.org/api_keys
@dnnsmnstrr
Copy link
Author

dnnsmnstrr commented Feb 5, 2025

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

@dnnsmnstrr
Copy link
Author

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