Задача «Проснуться утром» для меня, честно говоря, довольно сложная. Неделю назад пришла в голову идея: написать простой будильник, который будет проигрывать музыку все громче и громче пока не решишь математический пример.
В гугле есть некоторые статьи и темы, но все не то, что нужно.
Сразу оговорюсь, что компьютер (в моём случае ноутбук) ночью находится в спящем режиме, поэтому ему потребуется немного зарядки на ночь (у меня за ночь уходит около 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, чтобы можно было скачать.
Надеюсь этот будильник заставит не проспать на учёбу или работу :)
Автор: Spencer