Improve Dockerfile, compose configuration, Kindle script, and UI styles for better maintainability and functionality

This commit introduces several enhancements across multiple files to improve maintainability, functionality, and overall code quality. Key updates are as follows:
	1.	.gitignore
	•	Added exclusion for kindle/data/* to prevent Kindle-specific data from being tracked.
	2.	Dockerfile
	•	Added installation of OpenMoji font:
	•	Downloads and unzips OpenMoji font to /usr/share/fonts/openmoji.
	•	Updates font cache with fc-cache.
	•	Verifies installation with fc-list | grep "OpenMoji".
	•	Improves container setup for rendering Kindle dashboards with proper font support.
	3.	compose.yml
	•	Port mapping now uses an environment variable ${HOST_PORT} instead of hardcoding 56733:80.
	•	Enhances flexibility for port management.
	4.	kindle/ascwebdash.py
	•	Significant refactoring and cleanup:
	•	Removed unused signal handling and daemonization logic.
	•	Simplified logging configuration and added optional arguments using argparse for flexibility.
	•	Abstracted repetitive paths using constants for maintainability.
	•	Consolidated functionality for fetching images and refreshing the screen.
	•	Removed unused Wi-Fi and GUI toggling code.
	•	Focused the script’s functionality on image fetching and display for Kindle extensions.
	5.	CSS (style.css)
	•	Removed visual debugging borders (red, green, yellow).
	•	Improved layout styles:
	•	Set fixed widths for better rendering of event columns.
	•	Adjusted margins and paddings for cleaner alignment.
	•	Added a new .day .events class for consistent padding.
	6.	views.py
	•	Added error handling when fetching calendar data:
	•	Ensures the application doesn’t crash if calendar URLs are inaccessible.
	•	Logs errors to console for debugging.

Impact:
	•	Maintainability: Refactored scripts, improved code structure, and enhanced readability.
	•	Flexibility: Environment variable support for ports and dynamic script arguments.
	•	Functionality: Added OpenMoji font support in the Docker container.
	•	UI/UX: Cleaned up CSS for better layout and appearance.
	•	Resilience: Improved error handling in views.py to handle calendar fetch failures gracefully.

Files Modified:
	•	.gitignore
	•	Dockerfile
	•	compose.yml
	•	kindle/ascwebdash/bin/asc-webdash.py
	•	server/app/templates/style.css
	•	server/app/views.py

This ensures better extensibility and robustness across the codebase.
This commit is contained in:
2024-12-17 06:59:54 -06:00
parent d8d9e3bbab
commit e96ef0e44e
6 changed files with 66 additions and 150 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,7 @@
/test.py /test.py
kindle/data/*
__pycache__ __pycache__
*.py[cod] *.py[cod]
*$py.class *$py.class

View File

@@ -7,3 +7,10 @@ RUN apt-get update && apt-get install -y \
COPY ./requirements.txt /var/www/requirements.txt COPY ./requirements.txt /var/www/requirements.txt
COPY ./server ./app COPY ./server ./app
RUN pip install -r /var/www/requirements.txt RUN pip install -r /var/www/requirements.txt
RUN wget -O /tmp/openmoji-font.zip https://github.com/hfg-gmuend/openmoji/releases/download/15.0.0/openmoji-font.zip \
&& unzip /tmp/openmoji-font.zip -d /usr/share/fonts/openmoji \
&& rm /tmp/openmoji-font.zip
RUN fc-cache -f -v
RUN fc-list | grep "OpenMoji"

View File

@@ -4,7 +4,7 @@ services:
env_file: env_file:
- .env - .env
ports: ports:
- "56733:80" - "${HOST_PORT}:80"
volumes: volumes:
- ./server:/app - ./server:/app

View File

@@ -2,27 +2,27 @@
# encoding: utf-8 # encoding: utf-8
import os import os
import sys
import time import time
import signal
import requests import requests
import logging import logging
import subprocess import subprocess
import argparse
# Configuration # Configuration Defaults
LOG_FILE = "/tmp/daemon.log" EXTENSION_NAME="ascwebdash"
EXTENSION_DIR=f"/mnt/us/extensions/{EXTENSION_NAME}"
LOG_FILE = f"{EXTENSION_DIR}/asc-webdash.py.log"
IMG_URL = "http://10.0.1.113:56733/image" IMG_URL = "http://10.0.1.113:56733/image"
IMAGE_PATH = "/mnt/us/extensions/ascwebdash/data/received_image.png" IMAGE_PATH = f"{EXTENSION_DIR}/received_image.png"
PID_FILE = "/tmp/mydaemon.pid"
REFRESH_SECONDS=1800 REFRESH_SECONDS=1800
# Configure logging # Configure logging
logging.basicConfig( # logging.basicConfig(
filename=LOG_FILE, # filename=LOG_FILE,
level=logging.INFO, # level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s", # format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S", # datefmt="%Y-%m-%d %H:%M:%S",
) # )
def fetch_image_from_server(url, save_path): def fetch_image_from_server(url, save_path):
"""Fetch a IMG file from an HTTP server.""" """Fetch a IMG file from an HTTP server."""
@@ -39,7 +39,7 @@ def fetch_image_from_server(url, save_path):
logging.error(f"Failed to fetch IMG: {e}") logging.error(f"Failed to fetch IMG: {e}")
return False return False
def display_image_with_fbink(image_path): def refresh_screen_with_image(image_path):
"""Display the IMG image on the Kindle using FBInk.""" """Display the IMG image on the Kindle using FBInk."""
logging.info(f"Displaying image: {image_path}") logging.info(f"Displaying image: {image_path}")
try: try:
@@ -48,23 +48,6 @@ def display_image_with_fbink(image_path):
except Exception as e: except Exception as e:
logging.error(f"Failed to display image: {e}") logging.error(f"Failed to display image: {e}")
def write_pid():
with open(PID_FILE, "w") as f:
f.write(str(os.getpid()))
def handle_signal(signum, frame):
"""Handle incoming signals."""
if signum == signal.SIGHUP:
logging.info("Received SIGHUP signal: Refreshing state...")
set_wifi(True)
logging.info("Fetching image")
display_image_with_fbink(IMAGE_PATH)
display_battery_percentage(get_battery_percentage())
set_wifi(False)
elif signum == signal.SIGTERM:
logging.info("Received SIGTERM signal: Exiting daemon...")
sys.exit(0)
def set_deep_sleep(enable: bool): def set_deep_sleep(enable: bool):
""" """
Enable or disable deep sleep on the Kindle. Enable or disable deep sleep on the Kindle.
@@ -74,9 +57,12 @@ def set_deep_sleep(enable: bool):
""" """
try: try:
# Map boolean argument to corresponding lipc-set-prop value # Map boolean argument to corresponding lipc-set-prop value
lipc_bin = "lipc-set-prop"
service = "com.lab126.powerd"
prop = "preventScreenSaver"
value = "0" if enable else "1" value = "0" if enable else "1"
subprocess.run( subprocess.run(
["lipc-set-prop", "com.lab126.powerd", "preventScreenSaver", value], [lipc_bin, service, prop, value],
check=True, check=True,
) )
action = "enabled" if enable else "disabled" action = "enabled" if enable else "disabled"
@@ -91,8 +77,11 @@ def get_battery_percentage():
"""Retrieve the Kindle's battery percentage.""" """Retrieve the Kindle's battery percentage."""
try: try:
# Run the command to get battery percentage # Run the command to get battery percentage
lipc_bin = "lipc-get-prop"
service = "com.lab126.powerd"
prop = "preventScreenSaver"
result = subprocess.run( result = subprocess.run(
["lipc-get-prop", "com.lab126.powerd", "battLevel"], [lipc_bin, service, prop],
capture_output=True, capture_output=True,
text=True, text=True,
check=True, check=True,
@@ -110,124 +99,37 @@ def display_battery_percentage(battery_percentage):
message = f"{battery_percentage}%" message = f"{battery_percentage}%"
# Display the message using FBInk # Display the message using FBInk
os.system(f"fbink -x 10 -p {message} > /dev/null 2>&1") os.system(f"fbink -p {message} > /dev/null 2>&1")
logging.debug("Battery percentage displayed successfully.") logging.debug("Battery percentage displayed successfully.")
except Exception as e: except Exception as e:
logging.debug(f"Error displaying battery percentage: {e}") logging.debug(f"Error displaying battery percentage: {e}")
def send_sighup():
"""Send SIGHUP signal to the daemon."""
if not os.path.exists(PID_FILE):
logging.debug("Daemon is not running (PID file not found).")
return
try:
# Read the PID from the file
with open(PID_FILE, "r") as f:
pid = int(f.read().strip())
# Send the SIGHUP signal
os.kill(pid, signal.SIGHUP)
logging.debug("SIGHUP signal sent to the daemon.")
except Exception as e:
logging.debug(f"Failed to send SIGHUP: {e}")
def set_gui(enable: bool):
"""
Start or stop the Kindle GUI based on the enable input.
Args:
enable (bool): If True, starts the GUI. If False, stops the GUI.
"""
action = "start" if enable else "stop"
try:
subprocess.run([action, "lab126_gui"], check=True)
logging.info(f"GUI {action}ped successfully.")
except subprocess.CalledProcessError as e:
logging.info(f"Failed to {action} GUI: {e}")
def set_wifi(enable: bool):
"""
Toggle Wi-Fi on or off based on the `enable` boolean input.
Args:
enable (bool): If True, Wi-Fi will be turned on. If False, Wi-Fi will be turned off.
"""
try:
if enable:
# Enable Wi-Fi
# logging.info("Starting Wi-Fi...")
# subprocess.run(["lipc-set-prop", "com.lab126.cmd", "wirelessEnable", "1"], check=True)
# time.sleep(60)
# logging.info("Wi-Fi has been enabled.")
pass
else:
# Disable Wi-Fi
# subprocess.run(["lipc-set-prop", "com.lab126.cmd", "wirelessEnable", "0"], check=True)
# logging.info("Wi-Fi has been disabled.")
pass
except subprocess.CalledProcessError as e:
logging.error(f"Failed to toggle Wi-Fi: {e}")
def main(): def main():
"""Main daemon process.""" """Main daemon process."""
logging.info("Daemon started") parser = argparse.ArgumentParser()
# Define optional arguments
parser.add_argument("--extension-dir", type=str, default=EXTENSION_DIR,
help=f"Directory for extensions (default: {EXTENSION_DIR})")
parser.add_argument("--log-file", type=str, default=LOG_FILE,
help=f"Path to the log file (default: {LOG_FILE})")
parser.add_argument("--img-url", type=str, default=IMG_URL,
help=f"URL of the image to download (optional default:{IMG_URL})")
parser.add_argument("--image-path", type=str, default=IMAGE_PATH,
help=f"Path to save the image (default: {IMAGE_PATH})")
parser.add_argument("--refresh-seconds", type=int, default=REFRESH_SECONDS,
help=f"Time in seconds between refreshes (default: {REFRESH_SECONDS})")
logging.info("Script started")
while True: while True:
if fetch_image_from_server(IMG_URL, IMAGE_PATH): if fetch_image_from_server(IMG_URL, IMAGE_PATH):
set_wifi(True)
logging.info("Fetching image") logging.info("Fetching image")
display_image_with_fbink(IMAGE_PATH) refresh_screen_with_image(IMAGE_PATH)
display_battery_percentage(get_battery_percentage()) display_battery_percentage(get_battery_percentage())
set_wifi(False)
else: else:
logging.error("Could not fetch or display image.") logging.error("Could not fetch or display image.")
time.sleep(REFRESH_SECONDS) # Sleep before fetching again time.sleep(REFRESH_SECONDS) # Sleep before fetching again
if __name__ == "__main__": if __name__ == "__main__":
# Handle termination signals
signal.signal(signal.SIGTERM, handle_signal)
signal.signal(signal.SIGINT, handle_signal)
signal.signal(signal.SIGHUP, handle_signal)
# Process command-line arguments # Process command-line arguments
if len(sys.argv) > 1: set_deep_sleep(False)
if sys.argv[1] == "start": main()
# Write PID file and start the daemon
write_pid()
set_deep_sleep(False)
set_gui(False)
main()
elif sys.argv[1] == "stop":
# Stop the daemon
if os.path.exists(PID_FILE):
with open(PID_FILE, "r") as f:
pid = int(f.read())
os.kill(pid, signal.SIGTERM)
os.remove(PID_FILE)
set_deep_sleep(True)
set_gui(True)
logging.debug("Daemon stopped.")
else:
logging.debug("PID file not found. Is the daemon running?")
elif sys.argv[1] == "refresh":
if os.path.exists(PID_FILE):
send_sighup()
else:
logging.debug("PID file not found. Is the daemon running?")
elif sys.argv[1] == "restart":
# Restart the daemon
if os.path.exists(PID_FILE):
with open(PID_FILE, "r") as f:
pid = int(f.read())
os.kill(pid, signal.SIGTERM)
os.remove(PID_FILE)
logging.debug("Daemon stopped.")
# Start the daemon
write_pid()
main()
else:
logging.debug("Invalid command. Use 'start', 'stop', or 'restart'.")
else:
logging.debug("Usage: python3 mydaemon.py [start|stop|restart]")

View File

@@ -10,12 +10,9 @@ html, body {
body { body {
width: 600px; width: 600px;
height: 800px; height: 800px;
border: 2px solid red; /* Visual aid */
} }
.dashboard { .dashboard {
border: 2px solid green; /* Visual aid */
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
@@ -77,7 +74,6 @@ body {
} }
.panel.bottom.week .row { .panel.bottom.week .row {
border: 2px solid yellow; /* Visual aid */
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
@@ -102,6 +98,7 @@ body {
justify-content: flex-start; /* Align children at the top */ justify-content: flex-start; /* Align children at the top */
text-align: left; text-align: left;
flex: 1; flex: 1;
width: 148px;
} }
.day .day-title { .day .day-title {
@@ -119,11 +116,15 @@ body {
} }
.day .event { .day .event {
margin: 3pt;
background-color: white; background-color: white;
border-style: solid; border-style: solid;
border-radius: 5px; border-radius: 5px;
padding: 2px; padding: 2px;
margin-bottom: 2px;
}
.day .events {
padding: 2px;
} }
.footer { .footer {

View File

@@ -56,10 +56,14 @@ def dashboard():
for url in [ for url in [
'https://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics', 'https://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics',
]: ]:
r = requests.get(url) try:
c = cal.Calendar.from_ical(r.content) raise Exception
r = requests.get(url)
c = cal.Calendar.from_ical(r.content)
events += Event.fromIcalendar(c) events += Event.fromIcalendar(c)
except Exception as e:
print(f"Error: {e}")
with caldav.DAVClient(url=caldav_url, username=username, password=password) as client: with caldav.DAVClient(url=caldav_url, username=username, password=password) as client:
my_principal = client.principal() my_principal = client.principal()