Script Idea: Wake up every x minutes, check for messages, go to sleep if there are no new ones

I have put together a script that sets a wakeup with the rtc in 10 minutes before a standby.
After waking up, it checks whether the phone was woken up by the rtc.
If this is the case, it checks for new messages, if there are none, a new standby is initiated.

I have not yet had any experience of how much it drains the battery etc. But basically it seems to work.

I would be interested in your opinion.

Wakeup 10 Minutes after Standby:

/etc/rtcwake_config.conf

# Default wake-up time (in seconds) during the day
DAYTIME_WAKEUP_SECONDS=600  # 10 minutes

# Night wake-up time (in seconds)
NIGHTTIME_WAKEUP_SECONDS=3600  # 60 minutes

# Defines the time range for the night (from hour 0 to hour 6)
NIGHT_START_HOUR=0
NIGHT_END_HOUR=6

/usr/local/bin/set-rtc-wakeup.sh

#!/bin/bash

# Source the configuration file
source /etc/rtcwake_config.conf

# Get the current hour (24-hour format)
HOUR=$(date +%H)

# Default wake-up time: 10 minutes (for daytime)
WAKE_AFTER_SECONDS=$DAYTIME_WAKEUP_SECONDS

# If it's nighttime (from 00:00 to 06:00), use the night wake-up time
if [ "$HOUR" -ge "$NIGHT_START_HOUR" ] && [ "$HOUR" -lt "$NIGHT_END_HOUR" ]; then
    WAKE_AFTER_SECONDS=$NIGHTTIME_WAKEUP_SECONDS
fi

# Optional debug log (can be removed if not needed)
echo "[RTCWAKE] Hour: $HOUR, Wake in $WAKE_AFTER_SECONDS seconds" >> /var/log/rtcwake.log

# Set the RTC wake-up time
echo 0 > /sys/class/rtc/rtc0/wakealarm
rtcwake -m no -s $WAKE_AFTER_SECONDS

/etc/systemd/system/set-rtc-before-suspend.service

[Unit]
Description=Set RTC Wakeup before suspend
Before=sleep.target
ConditionPathExists=/usr/local/bin/set-rtc-wakeup.sh

[Service]
Type=oneshot
ExecStart=/usr/local/bin/set-rtc-wakeup.sh

[Install]
WantedBy=sleep.target

After standby and Wakeup from rtc, check For Messages, go to sleep if no new messages:

/etc/wakeup-check.conf

# Configuration for wakeup-check script

# Max time (in seconds) to wait for a working internet connection
MAX_WAIT=60

# Time (in seconds) to listen for incoming notifications before going to standby
NOTIFICATION_TIMEOUT=30

# Minutes until the next RTC wakeup if no notifications arrive
NEXT_RTC_WAKE_MIN=10

# Path to the log file
LOGFILE="/var/log/wakeup-check.log"

/usr/local/bin/wakeup-check.sh

#!/bin/bash

# ------------------ Load Config -------------------
CONFIG_FILE="/etc/wakeup-check.conf"

if [ ! -f "$CONFIG_FILE" ]; then
    echo "Config file $CONFIG_FILE not found!"
    exit 1
fi

source "$CONFIG_FILE"

# ------------------ Logging Function ----------------
log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOGFILE"
}

# ------------------ Check RTC Wakeup ----------------
is_rtc_wakeup() {
    journalctl -k --since "2 minutes ago" | grep -qi "rtc"
}

# ------------------ Set RTC Wakeup ------------------
set_rtc_wakeup() {
    WAKEUP_TS=$(( $(date +%s) + ($NEXT_RTC_WAKE_MIN * 60) ))
    echo 0 > /sys/class/rtc/rtc0/wakealarm
    echo "$WAKEUP_TS" > /sys/class/rtc/rtc0/wakealarm
    log "Next RTC wakeup set in $NEXT_RTC_WAKE_MIN minutes."
}

# ------------------ Wait for Internet ------------------
wait_for_internet() {
    log "Checking internet connection via nmcli (max. $MAX_WAIT seconds)..."
    for ((i = 0; i < MAX_WAIT; i++)); do
        status=$(nmcli networking connectivity)
        log "nmcli status: $status"
        if [[ "$status" == "full" ]]; then
            log "Full internet connection detected."
            return 0
        fi
        sleep 1
    done

    log "nmcli failed – trying ping fallback..."
    if ping -q -c 1 -W 2 8.8.8.8 >/dev/null; then
        log "Ping successful – internet likely available."
        return 0
    else
        log "Ping failed – no internet available."
        return 1
    fi
}

# ------------------ Main Logic ----------------------
log "---------- Wakeup script started ----------"

if is_rtc_wakeup; then
    log "RTC wakeup detected."

    # Start notification monitor in background
    TMP_NOTIFY_FILE=$(mktemp)
    log "Listening for notifications for $NOTIFICATION_TIMEOUT seconds..."
    (timeout "$NOTIFICATION_TIMEOUT" dbus-monitor "interface='org.freedesktop.Notifications'" | grep -q "member=Notify" && echo "NOTIFIED" > "$TMP_NOTIFY_FILE") &

    # Simultaneously wait for internet
    wait_for_internet
    INTERNET_READY=$?

    wait # wait for dbus-monitor to finish

    if grep -q "NOTIFIED" "$TMP_NOTIFY_FILE"; then
        log "Notification received – staying awake."
    else
        log "No notifications received."
        if [ "$INTERNET_READY" -eq 0 ]; then
            set_rtc_wakeup
            log "Going to suspend..."
            systemctl suspend
        else
            log "No internet – staying awake for safety."
        fi
    fi

    rm -f "$TMP_NOTIFY_FILE"
else
    log "Wakeup not caused by RTC – likely user interaction. Staying awake."
fi

log "---------- Wakeup script finished ----------"

/etc/systemd/system/wakeup-check.service

[Unit]
Description=Wakeup Checker – detects RTC wakeup and suspends if idle
After=suspend.target network-online.target
Requires=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/wakeup-check.sh

[Install]
WantedBy=suspend.target
3 Likes

I prefer manually turning on and off the device and/or hardware kill switches, so I have no use case for an automated script.

You gotta use a different IP address, dude! I don’t want to tell Google every time I wake up.

For random usage, I myself would typically type 1.1.1.1 (operated by Cloudflare). It is just as memorable, presumably just as reliable, but marginally less creepy.

I suggest that you make the IP address configurable. I myself would then choose one of my VPSs for this type of application. Nowhere near as reliable as Google or Cloudflare but zero creepiness. :slight_smile:

Also, bear in mind that a person who is out and about might have turned off mobile data - so ping will fail regardless. The phone can still make/receive calls and (presumably for now) send/receive SMSs. So it might be able to generate notifications. There also seem to be bug scenarios where mobile data turns itself off.

It wasn’t really clear to me what kind of “messages” and what kind of “notifications” you are interested in.

1 Like

…and these will already wake the phone up without the need of any script.

2 Likes

The good deal! But the main question for me was how to prevent the display to wake up?

Another idea - fail to sleep back in, e.g., 30 sec if the phone has not been unlocked and clear he state in another case.

1 Like

The answer will probably be hidden within gnome-settings-daemon.

1 Like

Ideas:
*LED changes color when there is a notification. @dos possible? Maybe then also go back to sleep immediately after recognized notifications.
*IP-adjustable for ping.
*Do not wake up screen during the exam.
*Whitelist or blacklist for wakeup.

@irvinewade Text messages and calls would probably be candidates for a blacklist. I’m talking about notifications that can’t wake up the device. Like signal, or flare, or other messengers, for example.

3 Likes

I did a bit of tinkering.
the monitor stays off while the script is running.

Before the system goes into standby, the rtc is set to a wakeup time that can be configured in minutes.
If this wakeup time is in the quiet hours (at night), it is set to the end of this time.
If there is an alarm clock that lies in the time until the next rtc start, the rtc start is set before the alarm clock start (configurable).

if the system wakes up, it is checked whether it was an rtc wakeup.
if it was an rtc wakeup, it is checked whether there were relevant messages, if not, the system goes back to standby and pulls up the rtc again.

If there were relevant messages (whitelist), the monitor is switched on.


i would like to use the led instead of switching on the display, but i haven’t found a way to do this yet.

wakeup-check.conf

# === Allgemeine Einstellungen ===
LOGFILE="/var/log/wakeup-check.log"
WAKE_TIMESTAMP_FILE="/var/log/rtc_wake_timestamp.txt"

# === Wakeup-Logik ===
NEXT_RTC_WAKE_MIN=15                 # Regulärer RTC-Wakeup alle X Minuten außerhalb der Quiet Hours
WAKE_BEFORE_ALARM_MINUTES=2         # Minuten vor einem Alarm, zu dem RTC aktiviert wird

# Zeitfenster zur Erkennung eines RTC-Wakeups (Sekunden)
RTC_WAKE_WINDOW_SECONDS=60

# === Quiet Hours ===
QUIET_HOURS_START="23:00"
QUIET_HOURS_END="06:00"

# === Internetverbindung prüfen ===
MAX_WAIT=30                         # Sekunden zum Warten auf Internet (über nmcli)
PING_HOST="1.1.1.1"                 # Fallback Host für Ping

# === Notification-Handling ===
NOTIFICATION_TIMEOUT=20             # Sekunden, um auf neue DBus-Notifications zu warten
APP_WHITELIST="signal flare"        # Relevante App-Namen in Kleinbuchstaben
NOTIFICATION_USE_FBCLI=true         # Ob fbcli zur Benachrichtigung verwendet wird
NOTIFICATION_TURN_ON_DISPLAY=true   # Bildschirm einschalten bei Notification

# === Zielbenutzer ===
TARGET_USER="dein-benutzername"     # Benutzername mit aktiver DBus-Session (z. B. der Desktop-User)

wakeup-check-pre.service

[Unit]
Description=Wakeup Check - Pre Suspend
Before=sleep.target
StopWhenUnneeded=true

[Service]
Type=oneshot
ExecStart=/usr/local/bin/wakeup-check.sh pre

[Install]
WantedBy=sleep.target


wakeup-check-post.service

[Unit]
Description=Wakeup Check - Post Suspend
After=suspend.target sleep.target
Requires=suspend.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/wakeup-check.sh post

[Install]
WantedBy=suspend.target

/usr/local/bin/wakeup-check.sh

#!/bin/bash

# Konfigurationsdatei laden
CONFIG_FILE="/etc/wakeup-check.conf"
[ ! -f "$CONFIG_FILE" ] && echo "Fehlende Konfigurationsdatei: $CONFIG_FILE" && exit 1
source "$CONFIG_FILE"

# Zielbenutzer UID und DBus-Session prüfen
TARGET_UID=$(id -u "$TARGET_USER")
if [ ! -d "/run/user/${TARGET_UID}" ]; then
    echo "DBus-Session für Benutzer ${TARGET_USER} nicht gefunden!"
    exit 1
fi
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/${TARGET_UID}/bus"

log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOGFILE"
}

turn_off_display() {
    log "Turning off display"
    sudo -u "$TARGET_USER" DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" \
        gdbus call --session --dest org.gnome.ScreenSaver \
        --object-path /org/gnome/ScreenSaver \
        --method org.gnome.ScreenSaver.SetActive true >/dev/null
}

turn_on_display() {
    log "Turning on display"
    sudo -u "$TARGET_USER" DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" \
        gdbus call --session --dest org.gnome.ScreenSaver \
        --object-path /org/gnome/ScreenSaver \
        --method org.gnome.ScreenSaver.SetActive false >/dev/null
}

use_fbcli() {
    if [ "$NOTIFICATION_USE_FBCLI" == "true" ]; then
        log "Using fbcli for notification."
        sudo -u "$TARGET_USER" fbcli -E notification-missed-generic
        sudo -u "$TARGET_USER" fbcli -E message-new-instant
    fi
}

is_rtc_wakeup() {
    [ -f "$WAKE_TIMESTAMP_FILE" ] || return 1
    last_wake=$(cat "$WAKE_TIMESTAMP_FILE")
    now=$(date +%s)
    diff=$((now - last_wake))
    (( diff >= 0 && diff <= RTC_WAKE_WINDOW_SECONDS ))
}

is_quiet_hours() {
    local now time_start time_end
    now=$(date +%H:%M)
    time_start="$QUIET_HOURS_START"
    time_end="$QUIET_HOURS_END"

    if [[ "$time_start" < "$time_end" ]]; then
        [[ "$now" > "$time_start" && "$now" < "$time_end" ]]
    else
        [[ "$now" > "$time_start" || "$now" < "$time_end" ]]
    fi
}

get_next_alarm_time() {
    gdbus call --session \
        --dest org.gnome.clocks \
        --object-path /org/gnome/clocks/AlarmModel \
        --method org.gnome.clocks.AlarmModel.ListAlarms 2>/dev/null |
        grep -oP '\d{10}' | sort -n | head -1
}

set_rtc_wakeup() {
    local now wake_ts quiet_end_ts next_alarm_ts
    now=$(date +%s)

    # Quiet hours Ende berechnen
    if [[ "$QUIET_HOURS_START" < "$QUIET_HOURS_END" ]]; then
        quiet_end_ts=$(date -d "today $QUIET_HOURS_END" +%s)
    else
        quiet_end_ts=$(date -d "tomorrow $QUIET_HOURS_END" +%s)
    fi

    next_alarm_ts=$(get_next_alarm_time)

    if is_quiet_hours; then
        log "Currently in quiet hours."

        if [[ "$next_alarm_ts" =~ ^[0-9]+$ && $next_alarm_ts -gt $now && $next_alarm_ts -lt $quiet_end_ts ]]; then
            wake_ts=$((next_alarm_ts - (WAKE_BEFORE_ALARM_MINUTES * 60) ))
            log "Next alarm during quiet hours. Setting RTC wakeup for $(date -d @$wake_ts)"
        else
            wake_ts=$quiet_end_ts
            log "No alarm during quiet hours. Setting RTC wakeup for end of quiet hours: $(date -d @$wake_ts)"
        fi
    else
        wake_ts=$(( now + (NEXT_RTC_WAKE_MIN * 60) ))
        log "Not in quiet hours. Setting RTC wakeup for $(date -d @$wake_ts)"
    fi

    echo 0 > /sys/class/rtc/rtc0/wakealarm
    echo "$wake_ts" > /sys/class/rtc/rtc0/wakealarm
    log "RTC wakeup set to $(date -d @$wake_ts)"
}

wait_for_internet() {
    log "Waiting up to $MAX_WAIT seconds for internet..."
    for ((i=0; i<MAX_WAIT; i++)); do
        status=$(nmcli networking connectivity)
        log "nmcli: $status"
        [[ "$status" == "full" ]] && log "Internet is available." && return 0
        sleep 1
    done

    if ping -q -c 1 -W 2 "$PING_HOST" >/dev/null; then
        log "Ping successful – internet likely available."
        return 0
    fi

    log "No internet connection detected."
    return 1
}

monitor_notifications() {
    local whitelist=($APP_WHITELIST)

    timeout "$NOTIFICATION_TIMEOUT" sudo -u "$TARGET_USER" DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" \
        dbus-monitor "interface='org.freedesktop.Notifications'" |
    while read -r line; do
        if echo "$line" | grep -q "member=Notify"; then
            buffer=""
            for _ in {1..6}; do read -r next && buffer+="$next"$'\n'; done
            app=$(echo "$buffer" | grep -oP 'string "\K[^"]+' | head -1 | tr '[:upper:]' '[:lower:]')
            log "Notification received from: $app"

            for match in "${whitelist[@]}"; do
                if [[ "$app" == *"$match"* ]]; then
                    log "Relevant notification from: $app"
                    [[ "$NOTIFICATION_TURN_ON_DISPLAY" == "true" ]] && turn_on_display
                    use_fbcli
                    echo "NOTIFIED"
                    return 0
                fi
            done

            log "Unlisted notification from: $app"
        fi
    done

    return 1
}

# --------- MAIN ---------

MODE="$1"
log "===== wakeup-check.sh started (mode: $MODE) ====="
turn_off_display

if [[ "$MODE" == "pre" ]]; then
    set_rtc_wakeup
    date +%s > "$WAKE_TIMESTAMP_FILE"
    log "Pre-mode done. Wake timestamp saved."
    log "===== wakeup-check.sh finished ====="
    exit 0
fi

if [[ "$MODE" == "post" ]]; then
    if is_rtc_wakeup; then
        log "RTC wakeup detected."

        if is_quiet_hours; then
            log "In quiet hours. Suspending again."
            set_rtc_wakeup
            systemctl suspend
            exit 0
        fi

        if wait_for_internet; then
            log "Checking for notifications..."
            TMP_NOTIFY_FILE=$(mktemp)
            (monitor_notifications > "$TMP_NOTIFY_FILE") &
            sleep "$NOTIFICATION_TIMEOUT"
            wait
            if grep -q "NOTIFIED" "$TMP_NOTIFY_FILE"; then
                log "Relevant notification found – staying awake."
            else
                log "No relevant notification – suspending again."
                set_rtc_wakeup
                systemctl suspend
            fi
            rm -f "$TMP_NOTIFY_FILE"
        else
            log "No internet – suspending again."
            set_rtc_wakeup
            systemctl suspend
        fi
    else
        log "Non-RTC wakeup – system stays awake."
    fi
fi

log "===== wakeup-check.sh finished ====="

I’m still testing a bit then I’ll upload the script.
i am not yet satisfied with deactivating desplay. do you have any ideas?

2 Likes

This might be of use to you:

2 Likes

I think I need to go back to the detection of what woke up the system.
Does anyone have any good ideas?

1 Like

Plenty of ones!
You could use systemd timers to wake up the phone and check is it expired or not:

$ cat /usr/lib/systemd/system/resume-delay.timer 
[Unit]
Description=Resume delay to check IMs

[Timer]
OnActiveSec=1min
AccuracySec=1s
WakeSystem=true

[Install]
WantedBy=timers.target

$ sudo systemctl restart resume-delay.timer
$ sudo systemctl suspend

Resume before the timer expired:

$ systemctl list-timers resume-delay.timer
NEXT                             LEFT LAST    PASSED UNIT             ACTIVATES                     
Fri 2025-04-18 10:13:03 +07       31s -       - resume-delay.timer    resume-delay.service

Resume by the timer:

$ systemctl list-timers resume-delay.timer
NEXT                             LEFT LAST    PASSED UNIT             ACTIVATES                     
Fri 2025-04-18 10:14:46 +07       20s -       - resume-delay.timer    resume-delay.service

There is one strange thing here. The list should look like this:

NEXT LEFT LAST                            PASSED     UNIT             ACTIVATES                     
-       - Fri 2025-04-18 10:14:46 +07     5min ago resume-delay.timer resume-delay.service

but in fact I see that the timer did not expire while the phone is already resumed… May be we need to use realtime timers instead of monitonic one, but I can not understand how to set it in a five minutes from now rather that run it each five minutes… Also you should check systemd version. The my one is 254.

1 Like

basically, the wakeup works very well with the rtc.

However, I have to clearly recognize whether the Librem5 was woken up by a source other than the rtc.
but so far I have not been able to find the wakeup reason in any log.

If it exists somewhere it would be perfect.

1 Like

$ rtcwake -m show

1 Like

that only tells me which wakeup time is stored in the rtc, doesn’t it?
This does not tell me whether the rtc has woken up the system or whether someone has pressed the button.

1 Like

But you could check is alarm expired when you are woken up or not, doesn’t it? I believe this is exactly that you need :slight_smile:

p.s. To be honest, I do not remember why I switched to use systemd timers rather that rtcwake in my previous investigations… Now I see at least one problem - it could conflict if someone else will use the same timer.

2 Likes

i’m looking for some bugs, i’ve broken something. if i’m satisfied, i’ll update it.

2 Likes