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

в 17:07, , рубрики: alarm, linux, linux bash scripts, будильник, метки: , ,

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

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

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

Сразу оговорюсь, что компьютер (в моём случае ноутбук) ночью находится в спящем режиме, поэтому ему потребуется немного зарядки на ночь (у меня за ночь уходит около 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js