Basic functionality and API call to ESV.org is complete along with the parsing of the returned object to something more presentable.

This commit is contained in:
Norm Rasmussen
2025-01-24 13:28:46 -05:00
commit dbd8ccbe5d
5 changed files with 212 additions and 0 deletions

25
README.md Normal file
View File

@ -0,0 +1,25 @@
# Bible Clock Project
As I was thinking about a gift for my partner, I saw an ad on Instagram a few weeks ago that was a simple box with a e-ink screen that simple displays a verse based on the time. So if it is 3:16, the screen would display the verse for a book in the Bible that contains a verse at chapter 3, verse 16. I thought that would be simple enough to build myself and give for her birthday.
The hardware is pretty easy to conceptualize (subject to change once I start putting wires to hardware):
* ESP powered device
* eInk display
* (optional) RTC sensor
Since Python is the language I'm the most comfortable with, I've been looking for a project to dip my feet into [Micropython](https://micropython.org/).
Current status:
* Logic for making an API call to [Crossway's ESV Endpoint](https://api.esv.org/)
* Parse returned JSON to make it more presentable (fixing special characters, closing open quotes, removing footnotes, etc)
* Add timeout decorator for times that are do not commonly have a verse/chapter association (i.e 11:58).
* Remove the 0 for any "xx:0x" timestamp as verses do not have 0s for numbers under 10.
To Do List:
- [ ] If timeout triggers, display generic verse on screen with the current time
- [ ] Check time every x seconds and run the functions if the time has changed from previous value.
- [ ] Setup micropython files & functions, including display libraries.
Longer term plans to make this "giftable":
- [ ] Create Wi-Fi SSID & Interface to connect to your own Wi-Fi network
- [ ] Web UI to remove/add specific books from being shown

Binary file not shown.

8
requirements.txt Normal file
View File

@ -0,0 +1,8 @@
certifi==2024.12.14
charset-normalizer==3.4.1
idna==3.10
levenshtein==0.26.1
pip==24.3.1
rapidfuzz==3.11.0
requests==2.32.3
urllib3==2.3.0

26
timeout.py Normal file
View File

@ -0,0 +1,26 @@
import errno
import os
import signal
import functools
class TimeoutError(Exception):
pass
def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
def decorator(func):
def _handle_timeout(signum, frame):
raise TimeoutError(error_message)
@functools.wraps(func)
def wrapper(*args, **kwargs):
signal.signal(signal.SIGALRM, _handle_timeout)
signal.alarm(seconds)
try:
result = func(*args, **kwargs)
finally:
signal.alarm(0)
return result
return wrapper
return decorator

153
verse-time-display.py Normal file
View File

@ -0,0 +1,153 @@
import requests
from Levenshtein import distance as lev
import time
import re
import random
import timeit
from timeout import timeout
import datetime
VERSION = "en-lsv"
APIKEY = "Token f562cf2d890151d682065696dacdc0f86938a18e"
HEADERS = {"Authorization": APIKEY}
BIBLE_BOOKS = [
"Genesis",
"Exodus",
"Leviticus",
"Numbers",
"Deuteronomy",
"Joshua",
"Judges",
"Ruth",
"1 Samuel",
"2 Samuel",
"1 Kings",
"2 Kings",
"1 Chronicles",
"2 Chronicles",
"Ezra",
"Nehemiah",
"Esther",
"Job",
"Psalm",
"Proverbs",
"Ecclesiastes",
"Song of Solomon",
"Isaiah",
"Jeremiah",
"Lamentations",
"Ezekiel",
"Daniel",
"Hosea",
"Joel",
"Amos",
"Obadiah",
"Jonah",
"Micah",
"Nahum",
"Habakkuk",
"Zephaniah",
"Haggai",
"Zechariah",
"Malachi",
"Matthew",
"Mark",
"Luke",
"John",
"Acts",
"Romans",
"1 Corinthians",
"2 Corinthians",
"Galatians",
"Ephesians",
"Philippians",
"Colossians",
"1 Thessalonians",
"2 Thessalonians",
"1 Timothy",
"2 Timothy",
"Titus",
"Philemon",
"Hebrews",
"James",
"1 Peter",
"2 Peter",
"1 John",
"2 John",
"3 John",
"Jude",
"Revelation",
]
def get_time():
gettime = datetime.datetime.now()
time = gettime.strftime("%H:%M")
return time
def choose_book():
rand_book = random.choice(BIBLE_BOOKS)
return rand_book
# @timeout(15)
def get_verse():
curr_time = get_time()
extract = ""
distance = lev(extract, curr_time)
while True:
book = choose_book()
time.sleep(2)
url = f"https://api.esv.org/v3/passage/text/?q={book} {curr_time}"
response = requests.get(url, headers=HEADERS)
query = response.json()["query"]
print(query)
chap_verse = re.search(r"(\d{1,3}:\d{1,3})", query)
try:
extract = chap_verse.group(0)
except AttributeError as a:
print(a)
distance = lev(extract, curr_time)
if "0" in curr_time and not curr_time.endswith("0"):
curr_time = curr_time.replace("0", "")
print(curr_time)
if distance < 1:
print(f"{extract} is equal to {curr_time}!")
break
return response
def parse_verse():
response = get_verse()
passage = response.json()["passages"]
verse = response.json()["query"]
pass_extract = re.split(r"\[\d{1,3}\]", str(passage[0]))
simplified = re.sub(r"(^\s|\s{2,}|\s\(ESV\)$)", "", pass_extract[1])
simplified2 = re.sub(r"(\\n|,[a-zA-Z])", " ", simplified)
if "(1)Footnotes" in simplified2:
new_list = simplified2.split("(1)Footnotes")[0]
print(new_list)
else:
new_list = simplified2
double_close = new_list.count('')
double_open = new_list.count('')
single_open = new_list.count("'")
single_open = new_list.count("")
single_closed = new_list.count('')
char_tuple = (double_open, double_close, single_open, single_closed)
if char_tuple == (1, 0, 0, 0):
new_list += ''
elif char_tuple == (1, 1, 1, 0):
new_list += ''
elif char_tuple == (1, 0, 1, 0):
new_list += '”’'
print(new_list)
print(verse)
if __name__ == "__main__":
parse_verse()