Пишем консольный будильник на BASH-е

  • Tutorial
Задача «Проснуться утром» для меня, честно говоря, довольно сложная. Неделю назад пришла в голову идея: написать простой будильник, который будет проигрывать музыку все громче и громче пока не решишь математический пример.




В гугле есть некоторые статьи и темы, но все не то, что нужно.

Сразу оговорюсь, что компьютер (в моём случае ноутбук) ночью находится в спящем режиме, поэтому ему потребуется немного зарядки на ночь (у меня за ночь уходит около 30% батареи).
Если у вас настольный компьютер, есть перебои с электричеством и нет ИБП, то лучше не рискуйте.

Далее вы должны убедиться, что ваш компьютер поддерживает некоторые режимы «сна». Можете почитать об этом здесь.

Что нам потребуется?

— Утилита rtcwake — для засыпания компьютера (встроена в ядро).
— Утилита amixer — для постепенного увеличения громкости звука.
— Любой проигрыватель, позволяющий воспроизводить музыку в цикле (в моём случае mplayer).

Начнём

Зададим необходимые переменные:
# время по умолчанию
tm='07:05'

# начальная громкость
volume=10

# максимальная громкость
volume_max=90

# время для смены задачи
sec=2

# папка с музыкой
folder=~username/Music/alarm/*

# временный файл для статуса
temp=`mktemp -t alarm_status_XXX.txt`

Здесь всё понятно.
«время для смены задачи» — время простоя перед генерацией новой задачи.
«папка с музыкой» — папка, из которой нужно брать список музыки. Я просто создал папку alarm и создал жёсткие ссылки на необходимые мне музыкальные композиции.
«временный файл для статуса» — нужен для завершения увеличения громкости. Если существует непустой файл, то завершаем фоновой процесс (кстати, можно просто через «jobs -l» найти наш процесс).

Теперь нужно написать функцию, которая будет убивать процессы mplayer-а и/или заново воспроизводить случайную музыку.
# включаем музыку
alarm_start()
{
	# убиваем все процессы mplayer-а
	jbs=(`ps al | grep [m]player | gawk -F ' ' '{print $3}'`)
	for job in ${jbs[*]} ; do
		kill -15 $jbs
	done
	
	# включаем случайную мелодию с бесконечным повтором
	if [ -z "$1" ] ; then
		mplayer -loop 0 -shuffle $folder &> /dev/null &
	fi
}

Также нужно перехватывать сигналы завершения процесса.
trap "echo -e '\nНеа, решите задачу!' && sleep 1 && alarm_start" SIGINT SIGTERM SIGHUP SIGQUIT SIGTSTP SIGSTOP

Для того, чтобы можно было указать время просыпания будем использовать необязательный параметр $1. Сразу сделаем все проверки валидности даты.
if [[ $# > 0 ]] ; then
	if [[ "$1" == [0-9]:[0-9][0-9] ]] || [[ "$1" == [0-9][0-9]:[0-9][0-9] ]] ; then
		tm=$1
	else
		echo 'Установите правильное время. Пример: "07:00".' >&2
		exit 10
	fi
fi

date1=$(date -d "`date +%m/%d/%y` $tm" +%s)
date2=$(date -d "`date +%m/%d/%y` $tm tomorrow" +%s)

# последняя ошибка (если неверная дата)
err=$?
if [[ $err > 0 ]] ; then
	echo 'Установите правильное время. Пример: "07:00".' >&2
	exit $err
fi

# если настоящее время больше времени для пробуждения, то ставим завтрашний день
if [[ $date1 < `date -u +%s` ]] ; then
	date=$date2
else
	date=$date1
fi

Переменные date1 и date2 нужны на случай, если пользователь укажет прошедшую дату, тогда время для просыпания будет установлено завтрашним днём. Если же время будет указано, например «07:99», то в переменную err будет занесён код ошибки.

Теперь можно «заставить» компьютер спать. rtcwake требует прав суперпользователя. Можно воспользоваться sudo. Всё, что вы делаете — вы делаете на свой страх и риск.
# засыпаем
sudo rtcwake -m mem -t $date

# устанавливаем громкость
amixer -q set Master $volume%

# включаем музыку
alarm_start

Про rtcwake, как уже говорил, вы можете прочитать здесь.

Осталось только сделать повышение громкости и пример для решения.
# повышаем уровень громкости
while true ; do
	amixer sset Master 1%+ &> /dev/null
	volume=$(( $volume+1 ))
	
	if [ $volume -eq $volume_max ] ; then
		break
	elif [ -s "$temp" ] ; then
		rm "$temp"
		
		# возвращаем нормальную громкость
		amixer -q set Master 50%
		
		break
	fi
	sleep 2
done &

Строчкой «amixer sset Master 1%+ &> /dev/null» мы указываем, что нужно повышать громкость на 1 процент и не нужно ничего выводить на экран.
Знак амперсанда & после оператора done нужен для того, чтобы увеличение громкости было в фоне.

И генерируем пример:
clear
echo 'Чтобы выключить музыку решите пример:'

while true ; do
	# ждём
	echo "Ждите $sec сек."
	sleep $sec
	
	# пример который надо решить
	var1=$(( $RANDOM % 10000 - 5000 ))
	var2=$(( ($RANDOM % 100000 - 50000)/($RANDOM % 800 + 1) ))
	
	# операторы
	case $(( $RANDOM % 3 )) in
		0)
			opt='+'
			result=$(( $var1 + $var2 ))
		;;
		1)
			opt='-'
			result=$(( $var1 - $var2 ))
		;;
		2)
			opt='*'
			var2=$(( ($RANDOM % 5 + 5) ))
			result=$(( $var1 * $var2 ))
		;;
	esac
	
	# для красоты
	if [[ $var2 < 0 ]] ; then
		if [[ "$opt" == '-' ]] ; then
			opt='+'
			var2=$(( $var2 * -1 ))
		elif [[ "$opt" == '+' ]] ; then
			opt='-'
			var2=$(( $var2 * -1 ))
		fi
	fi
	
	# ответ
	read -p "$var1 $opt $var2 = " answer
	
	# завершаем цикл если ответ был правильный
	if [[ $answer == $result ]] ; then
		echo "Правильно! Ответ: $result."
		break
	else
		clear
		echo -n "Неверно! Правильный ответ был: $var1 $opt $var2 = $result."
		if [ -n "$answer" ] ; then
			echo " Вы ответили: $answer."
		else
			echo ""
		fi
	fi
done

# параметром мы указываем, что не нужно воспроизводить музыку
alarm_start false

# для выключения увеличения громкости
echo "done" > "$temp"


Вот и всё. Будильник был написан неделю назад. Телефонные будильники были выключены. За эту неделю я вполне мог проснуться пока решаю пример (задачка может быть очень простой (1020 — 120) или сложной (394 * 5). В одно утро мне попадался 13 раз подряд пример с умножением).

Можно создать alias в файле ~/.bashrc: «alias alarm='~/path_to_script/alarm.sh'»
Для того, чтобы будильник нельзя было выключить закрытием программы консоли, я выхожу из текущего сеанса и переключаюсь в другую консоль (Alt+Ctrl+F1 или другой F[1-7]).
При желании будильник можно отключить подав сигнал принудительного закрытия, но для этого нужно будет зайти в другую консоль, авторизоваться, найти этот процесс и убить. Быстрее наверно решить пример :)

Полный код будильника
#!/bin/bash

# Будильник

# включаем музыку
alarm_start()
{
	# убиваем все процессы mplayer-а
	jbs=(`ps al | grep [m]player | gawk -F ' ' '{print $3}'`)
	for job in ${jbs[*]} ; do
		kill -15 $jbs
	done
	
	# включаем случайную мелодию с бесконечным повтором
	if [ -z "$1" ] ; then
		mplayer -loop 0 -shuffle $folder &> /dev/null &
	fi
}

# время по умолчанию
tm='07:05'

# начальная громкость
volume=10

# максимальная громкость
volume_max=90

# время для смены задачи
sec=2

# папка с музыкой
folder=~username/Music/alarm/*

# временный файл для статуса
temp=`mktemp -t alarm_status_XXX.txt`

# от намеренного закрытия сонного человека
trap "echo -e '\nНеа, решите задачу!' && sleep 1 && alarm_start" SIGINT SIGTERM SIGHUP SIGQUIT SIGTSTP SIGSTOP


if [[ $# > 0 ]] ; then
	if [[ "$1" == [0-9]:[0-9][0-9] ]] || [[ "$1" == [0-9][0-9]:[0-9][0-9] ]] ; then
		tm=$1
	else
		echo 'Установите правильное время. Пример: "07:00".' >&2
		exit 10
	fi
fi

date1=$(date -d "`date +%m/%d/%y` $tm" +%s)
date2=$(date -d "`date +%m/%d/%y` $tm tomorrow" +%s)

# последняя ошибка (если неверная дата)
err=$?
if [[ $err > 0 ]] ; then
	echo 'Установите правильное время. Пример: "07:00".' >&2
	exit $err
fi

# если настоящее время больше времени для пробуждения, то ставим завтрашний день
if [[ $date1 < `date -u +%s` ]] ; then
	date=$date2
else
	date=$date1
fi


# засыпаем
sudo rtcwake -m mem -t $date
# sudo echo "$date" > /sys/class/rtc/rtc0/wakealarm

# устанавливаем громкость
amixer -q set Master $volume%

# день недели
# day=$(( `date +%u` - 1 ))

# включаем музыку
alarm_start

# повышаем уровень громкости
while true ; do
	amixer sset Master 1%+ &> /dev/null
	volume=$(( $volume+1 ))
	
	if [ $volume -eq $volume_max ] ; then
		break
	elif [ -s "$temp" ] ; then
		rm "$temp"
		
		# возвращаем нормальную громкость
		amixer -q set Master 50%
		
		break
	fi
	sleep 2
done &

clear
echo 'Чтобы выключить музыку решите пример:'

while true ; do
	# ждём
	echo "Ждите $sec сек."
	sleep $sec
	
	# пример который надо решить
	var1=$(( $RANDOM % 10000 - 5000 ))
	var2=$(( ($RANDOM % 100000 - 50000)/($RANDOM % 800 + 1) ))
	
	# операторы
	case $(( $RANDOM % 3 )) in
		0)
			opt='+'
			result=$(( $var1 + $var2 ))
		;;
		1)
			opt='-'
			result=$(( $var1 - $var2 ))
		;;
		2)
			opt='*'
			var2=$(( ($RANDOM % 5 + 5) ))
			result=$(( $var1 * $var2 ))
		;;
	esac
	
	# для красоты
	if [[ $var2 < 0 ]] ; then
		if [[ "$opt" == '-' ]] ; then
			opt='+'
			var2=$(( $var2 * -1 ))
		elif [[ "$opt" == '+' ]] ; then
			opt='-'
			var2=$(( $var2 * -1 ))
		fi
	fi
	
	# ответ
	read -p "$var1 $opt $var2 = " answer
	
	# завершаем цикл если ответ был правильный
	if [[ $answer == $result ]] ; then
		echo "Правильно! Ответ: $result."
		break
	else
		clear
		echo -n "Неверно! Правильный ответ был: $var1 $opt $var2 = $result."
		if [ -n "$answer" ] ; then
			echo " Вы ответили: $answer."
		else
			echo ""
		fi
	fi
done

alarm_start false

# для выключения увеличения громкости
echo "done" > "$temp"


Для удобства код скопирован на pastebin, чтобы можно было скачать.

Надеюсь этот будильник заставит не проспать на учёбу или работу :)
Поделиться публикацией

Похожие публикации

Комментарии 44
    +5
    Яб колонки отключил и дальше спать
      0
      У кого ноутбук, те не смогут.
        +3
        Батарея вынимается.
          –5
          Батарея вынимается.

          Конечно, только во вред компьютеру.
            +1
            можно просто наушники вставить, всяко тише будет)
              0
              Если вы нашли наушники — значит вы уже проснулись :)
                +3
                Ну я их в принципе не вынимаю из ноута :)
                0
                В моём случае это будет вынуть наушники. В смысле из ушей. А так, да, конечно.
            +1
            У кого ноутбук, у тех есть отдельная кнопка Mute, или на худой конец, FN-комбинация.
              0
              В статье я упоминал, что лучше перейти в другую консоль сочетанием клавиш Ctrl+Alt+F(от 1 до 7). Fn там не работает.
                +1
                Странно. обычно Fn — аппаратная клавиша и хоткеи на ней обслужваются системой и далеко не всегда, кстати, передаются в ОС.
                0
                Кнопку нужно выломать.
                +1
                Классная статья, автор, мне очень понравилась, так держать. Надеюсь в будущем еще напишете ни одну такую полезную статью. Могу предложить пару тем: «как написать на баше 'привет мир' так что бы у нас скрипт открывал файл и зависал в системе» или «как написать скрипт который умеет складывать числа».
                  0
                  ой да ладно, не смогут — Fn+ иконка с изображением перечеркнутого динамика на одной из клавиш, ну или отдельная аппаратная клавиша и делов то.
                  Еще банальным втыканием наушников в порт весь вук перенаправляется в них в 99% случаев с выключением внешних динамиков.
                  И это так, навскидку.
                  У меня вот ноутбук при закрытии крышки вообще спать уходил — большая такая «аппаратная» кнопка выключения будильника.

                  Короче, как обычно — кто ищет отмазки, а кто решает проблемы. Мне вот телефонного будильника с головой хватает. Но и высыпаться начал, чаще просыпаюсь задолго до будильника.

                  а так — опыт по шелскриптам всегда позитивный, автор молодец
                +6
                «семь бед, один ресет» ну или power off
                  0
                  Ну так-то и телефон можно в стену кинуть, но все же (ну или, по крайней мере, большинство) просто выключают будильник, не?
                    0
                    Комментарий следовало оставить в треде первого отписавшего (про колонки). Извините, промахнулся. А вообще, если бы будильник на телефоне выключался столь же сложно, риск улететь в стену у него был бы отличен от нуля)
                      0
                      Когда в общаге у меня звонил будильник на телефоне и я не просыпался, мой сосед просто брал телефон и ложил его в холодильник — из-за уплотнителей там хорошая звукоизоляция.
                    +1
                    Это будет работать, пока не надоест наслаждаться своей поделкой.
                    Как написали выше, все решает классический лемовский прием выдергивания шнура из розетки. Так что дело двух ++ будильников по-прежнему живо.

                    Мне кажется, лучше отлова ритмов сна в этом деле придумать невозможно.
                      +2
                      Вот для телефона подобное использую, а для терминала…
                      shift+ctrl+t -> ~
                      $ echo -1843*7 | bc

                      и дальше спать…
                        +1
                        >jbs=(`ps al | grep [m]player | gawk -F ' ' '{print $3}'`)
                        > for job in ${jbs[*]}; do
                        > kill -15 $jbs
                        > done
                        Да, тяжело жить, если не знаешь про killall и pkill…
                          0
                          Запамятовал про killall…
                          0
                          Страховка от разбитого ноутбука

                          # crontab -e
                          06 07 * * * killall alarm.sh & killall amixer & killall mplayer
                            +1
                            Музыка играет от 10% до 90% с шагом 1% в 2 секунды, что я считаю медленным и вполне достаточным для решения примера.
                            Так же можно убавить максимальную громкость)
                            +1
                            Хорошо, конечно. Но полезность (для меня, например) — нулевая. Что только не пробовал, чтобы вставать вовремя по будильнику — даже системник и колонки размещал так, что не сразу выключишь, выдергивал регуляторы громкости на колонках, даже кнопки с системника и БП вытягивать — не помогало). После просыпания не помнил, как и когда вставал и выключал компьютер, либо звук. Помогло только одно — стал ложиться спать немного пораньше и встаю раньше положенного, но без будильника. Ну, а если лег позже — пипец :(.
                              0
                              Есть люди, которым для сна нужно всего-то 6-8 часов. Мне же для сна не хватит 8 часов. За неделю, благодаря этому скрипту, ни разу не проспал, хотя ложился около 00:00 или 01:00.
                              Ещё беда — заснуть долго не могу, независимо от времени.
                                0
                                Пейте перед сном чай зеленый травяной, еще можно за час, за два позаниматься физ. упраженениями. Самый простой способ уснуть это устать.
                              0
                              Я бы уточнил, что rtcwake может не работать как ожидается. Скажем, комп может не проснуться.
                                0
                                Так было
                                sleep 25200 && while true; do say "Эй, сосед, готоовь обед"; done;
                                

                                пока однажды сосед не сказал мне все, что он думает обо мне. Теперь только приятная музыка по утрам.
                                • НЛО прилетело и опубликовало эту надпись здесь
                                    0
                                    Когда-то использовал простой sh скрипт с содержанием вроде:

                                    mpc play

                                    Запускал просто: at %time ./alarm.sh

                                    На утро, чтобы отключить будильник, приходилось вводить свой пароль (lock screen). Хотя ложиться потом спать это всё-равно не мешало:)
                                      0
                                      Интересно, конечно, молодец.

                                      А всего-то вопрос силы воли.
                                        0
                                        Серьезно думаете, что скрипт который заставляет решить 23124+325, помешает нажать кнопку на колонке?
                                          0
                                          Тут нужен бОльший стимул:

                                          например, скрипт, который запускается за 10 минут до срабатывания скрипта на боевом сервере дропающего базу. Если ведете правильный ответ, то он удалит страшный дроп-скрипт с далекого сервера, и крон на том сервере отработает пустышку, а иначе вас все равно разбудят, но это уже будет другая история, и дай бог провайдер вас не подведет.

                                          Думаю с таким стимулом у вас будут чудесные цветные кошмары, и даже проснетесь сами без будилки.
                                            0
                                            А почему бы не использовать спикер на системной плате?
                                            Сейчас пытаюсь его использовать, но почему то не хочет он пищать…
                                              0
                                              pkill -9 alarm.sh
                                              и дальше спать
                                                0
                                                Ctrl+C?
                                                killall?
                                                Закрыть консольку? )
                                                  +1
                                                  Ctrl+C?
                                                  Не сработает, т.к. предусмотрен перехват сигналов.
                                                  killall?
                                                  Можно, но придётся переходить в другую консоль и авторизоваться.
                                                  Закрыть консольку? )
                                                  Если вы в консоли где нет графических элементов ( Ctrl+Alt+F[1-7] ), то не сработает :)
                                                    0
                                                    Если вы в консоли где нет графических элементов ( Ctrl+Alt+F[1-7] ), то не сработает :)

                                                    exit работает и там =)
                                                      +1
                                                      Его нужно писать, а некуда. Придётся переходить в другую консоль, именно в консоль, а не в графическую среду.
                                                        0
                                                        Ну ок. Почти сдался =)))

                                                        Тогда Ctrl+Alt+F2, потом killall, авторизация не всегда потребуется (у меня часто 2 и 3 задействованы). Ну и без иксов тоже редко на десктопе линукс будет, а на сервере будильник как-то ни к чему…
                                                          0
                                                          а на сервере будильник как-то ни к чему…
                                                          Никто и не говорил про сервер :)

                                                          У меня графическая среда на Ctrl+Alt+F7. Перед сном я просто завершаю сеанс, перехожу в tty1 (Ctrl+Alt+F1) и запускаю будильник.

                                                          Я делаю все возможное, чтобы утром проснуться, а не быстрее встать и выключить будильник.
                                                          Кстати, сеанс можно приостановить нажатием Ctrl+Z, останавливается воспроизведение музыки, но скрипт «зависает», тогда нужно переходить в другую консоль и убивать его уже там :)
                                                          Перехват сигналов работает, но не очень корректно по отношению к приостановке процесса (Ctrl+Z).
                                                            0
                                                            Кстати, я думал про Ctrl+Z, но код меня смутил =)

                                                            Перед сном я просто завершаю сеанс, перехожу в tty1 (Ctrl+Alt+F1) и запускаю будильник.

                                                            Это здорово, но вот мне обычно лень, если уж оставляю комп включённым, проделывать столько действий. ) Поэтому сеанс не завершён, tty1 не активирован, только монитор в спящем режиме. А запуск будильника тогда лучше по крону делать )
                                                              0
                                                              Кстати, я думал про Ctrl+Z, но код меня смутил =)
                                                              Перехват сигнала Ctrl+Z работает, но, к сожалению, некорректно…

                                                              На ночь компьютер уходит в спящий режим, меньше потребляет электричество, в отличие от того же cron-а :)

                                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                Самое читаемое