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
kindle/data/*
__pycache__
*.py[cod]
*$py.class

View File

@@ -6,4 +6,11 @@ RUN apt-get update && apt-get install -y \
&& rm -rf /var/lib/apt/lists/*
COPY ./requirements.txt /var/www/requirements.txt
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
ports:
- "56733:80"
- "${HOST_PORT}:80"
volumes:
- ./server:/app

View File

@@ -2,27 +2,27 @@
# encoding: utf-8
import os
import sys
import time
import signal
import requests
import logging
import subprocess
import argparse
# Configuration
LOG_FILE = "/tmp/daemon.log"
# Configuration Defaults
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"
IMAGE_PATH = "/mnt/us/extensions/ascwebdash/data/received_image.png"
PID_FILE = "/tmp/mydaemon.pid"
IMAGE_PATH = f"{EXTENSION_DIR}/received_image.png"
REFRESH_SECONDS=1800
# Configure logging
logging.basicConfig(
filename=LOG_FILE,
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
# logging.basicConfig(
# filename=LOG_FILE,
# level=logging.INFO,
# format="%(asctime)s [%(levelname)s] %(message)s",
# datefmt="%Y-%m-%d %H:%M:%S",
# )
def fetch_image_from_server(url, save_path):
"""Fetch a IMG file from an HTTP server."""
@@ -39,31 +39,14 @@ def fetch_image_from_server(url, save_path):
logging.error(f"Failed to fetch IMG: {e}")
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."""
logging.info(f"Displaying image: {image_path}")
try:
os.system(f"fbink -i {image_path} > /dev/null 2>&1")
logging.info("Image displayed successfully.")
except Exception as 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)
logging.error(f"Failed to display image: {e}")
def set_deep_sleep(enable: bool):
"""
@@ -74,9 +57,12 @@ def set_deep_sleep(enable: bool):
"""
try:
# 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"
subprocess.run(
["lipc-set-prop", "com.lab126.powerd", "preventScreenSaver", value],
[lipc_bin, service, prop, value],
check=True,
)
action = "enabled" if enable else "disabled"
@@ -91,8 +77,11 @@ def get_battery_percentage():
"""Retrieve the Kindle's battery percentage."""
try:
# Run the command to get battery percentage
lipc_bin = "lipc-get-prop"
service = "com.lab126.powerd"
prop = "preventScreenSaver"
result = subprocess.run(
["lipc-get-prop", "com.lab126.powerd", "battLevel"],
[lipc_bin, service, prop],
capture_output=True,
text=True,
check=True,
@@ -110,124 +99,37 @@ def display_battery_percentage(battery_percentage):
message = f"{battery_percentage}%"
# 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.")
except Exception as 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}")
logging.debug(f"Error displaying battery percentage: {e}")
def main():
"""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:
if fetch_image_from_server(IMG_URL, IMAGE_PATH):
set_wifi(True)
logging.info("Fetching image")
display_image_with_fbink(IMAGE_PATH)
refresh_screen_with_image(IMAGE_PATH)
display_battery_percentage(get_battery_percentage())
set_wifi(False)
else:
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__":
# 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
if len(sys.argv) > 1:
if sys.argv[1] == "start":
# 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]")
set_deep_sleep(False)
main()

View File

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

View File

@@ -56,10 +56,14 @@ def dashboard():
for url in [
'https://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics',
]:
r = requests.get(url)
c = cal.Calendar.from_ical(r.content)
try:
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:
my_principal = client.principal()