Justin McArdle
Published © CC BY-NC-SA

eInkCal - DIY Smart eInk Calendar

eInkCal is a smart color e-ink calendar powered by Raspberry Pi, designed to cut waste and make everyday surfaces greener.

AdvancedShowcase (no instructions)10 hours305
eInkCal - DIY Smart eInk Calendar

Things used in this project

Hardware components

Raspberry Pi Zero 2 W
Raspberry Pi Zero 2 W
×1
ePaper / eInk 13.3 inch eink display
x2
×1
High Accuracy Pi RTC (DS3231)
Seeed Studio High Accuracy Pi RTC (DS3231)
×1
Adafruit PowerBoost 1000C
Adafruit PowerBoost 1000C
×1

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free

Story

Read more

Custom parts and enclosures

Preview

Sketchfab still processing.

Schematics

Layout

Code

run code

SH
Main run code
#!/bin/bash

# E-Paper Calendar System Control Script (cron-based version)
# This version is intended to be run via cron every 60 minutes and at boot.
# It features a recovery mechanism for screen script failures with a maximum of 3 reboot attempts.
# Once a script reaches GAVE_UP, its recovery flag is retained (with an incremented tally)
# and that script is run last during the full run.
#
# Recovery flags are stored separately:
#   recovery_flag_runscreen1.txt for runscreen1.py
#   recovery_flag_runscreen2.txt for runscreen2.py
#
# Flag file format: script_name:attempt:status
#   - status can be: REBOOT_PENDING, REBOOT_COMPLETE, or GAVE_UP

# Set working directory to the script location
cd "$(dirname "$0")"

#######################################
# Function Definitions
#######################################

log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

log_error() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >> errorLog.txt
}

log_stat() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> statsLog.txt
}

run_script() {
    script_name=$1
    log_message "Running $script_name..."
    if python3 "$script_name"; then
        log_message "$script_name completed successfully"
        return 0
    else
        log_error "$script_name failed with exit code $?"
        return 1
    fi
}

run_screen_script_with_retry() {
    script_name=$1
    flag_file=$2
    
    max_attempts=3
    for attempt in $(seq 1 $max_attempts); do
        log_message "Running $script_name (attempt $attempt of $max_attempts)..."
        if python3 "$script_name"; then
            log_message "$script_name completed successfully on attempt $attempt"
            return 0
        else
            log_error "$script_name failed on attempt $attempt"
            if [ $attempt -lt $max_attempts ]; then
                log_message "Waiting 10 seconds before retrying $script_name..."
                sleep 10
            fi
        fi
    done
    
    log_error "$script_name failed after $max_attempts attempts"
    return 1
}

check_internet() {
    log_message "Checking internet connectivity..."
    attempt=1
    max_attempts=5
    while [ $attempt -le $max_attempts ]; do
        if ping -c 1 8.8.8.8 &>/dev/null; then
            log_message "Internet connection established on attempt $attempt"
            return 0
        else
            log_message "Internet check failed (attempt $attempt/$max_attempts). Retrying in 5 seconds..."
            sleep 5
            attempt=$((attempt+1))
        fi
    done
    log_error "Failed to establish internet connection after $max_attempts attempts"
    return 1
}

update_refresh_count() {
    if [ ! -f statsLog.txt ]; then
        echo "REFRESH_COUNT=0" > statsLog.txt
    fi
    current_count=$(grep "REFRESH_COUNT=" statsLog.txt | tail -1 | cut -d'=' -f2)
    new_count=$((current_count+1))
    log_stat "REFRESH_COUNT=$new_count"
    log_stat "LAST_REFRESH=$(date '+%Y-%m-%d %H:%M:%S')"
}

#######################################
# Early Recovery Mechanism
#######################################
# Process any recovery flag that is not in GAVE_UP state.
process_recovery_flag() {
    flag_file=$1
    if [ -f "$flag_file" ]; then
        IFS=":" read -r failed_script attempt status < "$flag_file"
        attempt=${attempt:-0}
        if [ "$status" != "GAVE_UP" ]; then
            log_message "Recovery mode: Detected $failed_script (attempt $attempt) with status $status in $flag_file"
            if [ "$status" == "REBOOT_PENDING" ]; then
                echo "$failed_script:$attempt:REBOOT_COMPLETE" > "$flag_file"
                log_message "Recovery mode: Updated status to REBOOT_COMPLETE in $flag_file. Rebooting now..."
                sudo reboot
                exit 0
            elif [ "$status" == "REBOOT_COMPLETE" ]; then
                new_attempt=$((attempt+1))
                if [ $new_attempt -ge 3 ]; then
                    echo "$failed_script:$new_attempt:GAVE_UP" > "$flag_file"
                    log_message "Recovery mode: Max attempts reached for $failed_script ($new_attempt). Marking as GAVE_UP and continuing full run."
                    # Do not exit; let full run proceed.
                else
                    echo "$failed_script:$new_attempt:REBOOT_PENDING" > "$flag_file"
                    log_message "Recovery mode: Incremented attempt to $new_attempt for $failed_script. Running recovery mode now..."
                    if run_screen_script_with_retry "$failed_script" "$flag_file"; then
                        log_message "Recovery mode: $failed_script succeeded. Clearing $flag_file."
                        rm "$flag_file"
                    else
                        log_error "Recovery mode: $failed_script failed on recovery attempt."
                    fi
                    exit 0
                fi
            fi
        else
            log_message "Recovery flag in $flag_file is in GAVE_UP state. Skipping early recovery processing."
        fi
    fi
}

# Check both recovery flag files for early recovery.
process_recovery_flag "recovery_flag_runscreen1.txt"
process_recovery_flag "recovery_flag_runscreen2.txt"

#######################################
# Main Execution (Cron-based, single run)
#######################################
touch errorLog.txt
touch statsLog.txt
log_message "Starting e-paper calendar system..."

if [ -f calConfig.json ]; then
    update_frequency=$(grep -o '"Update frequency (minutes)":[^,}]*' calConfig.json | cut -d':' -f2 | tr -d ' ')
    if ! [[ "$update_frequency" =~ ^[0-9]+$ ]]; then
        update_frequency=60
        log_message "Using default update frequency of 60 minutes"
    else
        log_message "Using configured update frequency of $update_frequency minutes"
    fi
else
    update_frequency=60
    log_message "Config file not found. Using default update frequency of 60 minutes"
fi

if check_internet; then
    update_refresh_count
    run_script fetchIcs.py
    run_script alarmSet.py
    run_script fetchData.py
    run_script imageSave.py
    run_script renderProcess.py

    ############################################################
    # Execute Screen Scripts  Normal vs. GAVE_UP Execution
    ############################################################

    # Flags to indicate if a script is in GAVE_UP mode.
    gave_up_screen1=false
    gave_up_screen2=false

    # --- Process runscreen2.py ---
    flag2="recovery_flag_runscreen2.txt"
    if [ -f "$flag2" ]; then
        IFS=":" read -r script2 attempt2 status2 < "$flag2"
        if [ "$status2" == "GAVE_UP" ]; then
            gave_up_screen2=true
            log_message "runscreen2.py is in GAVE_UP mode (attempt $attempt2). It will be run later."
        else
            # Not in GAVE_UP; overwrite with new attempt.
            echo "runscreen2.py:1:REBOOT_PENDING" > "$flag2"
            if run_screen_script_with_retry runscreen2.py "$flag2"; then
                rm "$flag2"
            else
                log_error "runscreen2.py failed even with retries. Initiating recovery reboot..."
                sudo reboot
            fi
        fi
    else
        # No existing flag; run normally.
        echo "runscreen2.py:1:REBOOT_PENDING" > "$flag2"
        if run_screen_script_with_retry runscreen2.py "$flag2"; then
            rm "$flag2"
        else
            log_error "runscreen2.py failed even with retries. Initiating recovery reboot..."
            sudo reboot
        fi
    fi

    # --- Process runscreen1.py ---
    flag1="recovery_flag_runscreen1.txt"
    if [ -f "$flag1" ]; then
        IFS=":" read -r script1 attempt1 status1 < "$flag1"
        if [ "$status1" == "GAVE_UP" ]; then
            gave_up_screen1=true
            log_message "runscreen1.py is in GAVE_UP mode (attempt $attempt1). It will be run later."
        else
            echo "runscreen1.py:1:REBOOT_PENDING" > "$flag1"
            if run_screen_script_with_retry runscreen1.py "$flag1"; then
                rm "$flag1"
            else
                log_error "runscreen1.py failed even with retries. Initiating recovery reboot..."
                sudo reboot
            fi
        fi
    else
        echo "runscreen1.py:1:REBOOT_PENDING" > "$flag1"
        if run_screen_script_with_retry runscreen1.py "$flag1"; then
            rm "$flag1"
        else
            log_error "runscreen1.py failed even with retries. Initiating recovery reboot..."
            sudo reboot
        fi
    fi

    ############################################################
    # Process any GAVE_UP screen scripts (execute them last)
    ############################################################

    # To ensure the full Run.sh cycle completes, any screen script in GAVE_UP mode is run now.
    # When running in GAVE_UP mode, we update (increment) the attempt count and run the script without rebooting.
    if $gave_up_screen1; then
        IFS=":" read -r script1 attempt1 status1 < "$flag1"
        new_attempt=$((attempt1+1))
        echo "runscreen1.py:$new_attempt:GAVE_UP" > "$flag1"
        log_message "Attempting runscreen1.py in GAVE_UP mode (attempt $new_attempt)..."
        if run_screen_script_with_retry runscreen1.py "$flag1"; then
            log_message "runscreen1.py succeeded in GAVE_UP mode. Clearing flag."
            rm "$flag1"
        else
            log_error "runscreen1.py failed in GAVE_UP mode. Tally updated (attempt $new_attempt)."
        fi
    fi

    if $gave_up_screen2; then
        IFS=":" read -r script2 attempt2 status2 < "$flag2"
        new_attempt=$((attempt2+1))
        echo "runscreen2.py:$new_attempt:GAVE_UP" > "$flag2"
        log_message "Attempting runscreen2.py in GAVE_UP mode (attempt $new_attempt)..."
        if run_screen_script_with_retry runscreen2.py "$flag2"; then
            log_message "runscreen2.py succeeded in GAVE_UP mode. Clearing flag."
            rm "$flag2"
        else
            log_error "runscreen2.py failed in GAVE_UP mode. Tally updated (attempt $new_attempt)."
        fi
    fi

    log_message "Calendar refresh completed."
    log_message "Shutting down the system..."
    sudo shutdown -h now
else
    log_message "Skipping refresh due to no internet connection."
    log_message "Shutting down the system..."
    sudo shutdown -h now
fi

Credits

Justin McArdle
1 project • 0 followers

Comments