commit dbd8ccbe5da2fb06a03c37c8f6cbc2c121b93e61 Author: Norm Rasmussen Date: Fri Jan 24 13:28:46 2025 -0500 Basic functionality and API call to ESV.org is complete along with the parsing of the returned object to something more presentable. diff --git a/README.md b/README.md new file mode 100644 index 0000000..bde0fde --- /dev/null +++ b/README.md @@ -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 diff --git a/__pycache__/timeout.cpython-313.pyc b/__pycache__/timeout.cpython-313.pyc new file mode 100644 index 0000000..dcd1402 Binary files /dev/null and b/__pycache__/timeout.cpython-313.pyc differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..093ac5d --- /dev/null +++ b/requirements.txt @@ -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 diff --git a/timeout.py b/timeout.py new file mode 100644 index 0000000..32f83d8 --- /dev/null +++ b/timeout.py @@ -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 diff --git a/verse-time-display.py b/verse-time-display.py new file mode 100644 index 0000000..1771532 --- /dev/null +++ b/verse-time-display.py @@ -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()