вторник, 9 сентября 2014 г.

Будильник из спящего режима для... ноутбука и ПК

Многие опытные пользователи наверняка успели по достоинству оценить преимущества спящего режима – это и практически мгновенная загрузка машины (так как не тратится время на загрузку всех программ, которые подгружаются при старте ОС Windows) и экономия времени. А знаете-ли вы, что ваша «рабочая лошадка» может будить вас или играть любимую мелодию (и даже видео) из спящего режима в назначенное время? Нет-нет, для этого не понадобится активация WOL и дополнительная машина в сетке и не понадобиться копание в настройках биоса. Нужны лишь так называемые таймеры пробуждения. Сегодня мы с вами узнаем что это такое и создадим простейший консольный будильничек из спящего режима ПК/ноутбука. Интересно? Тогда материал ниже для вас...

Вообще, таймеры пробуждения или ожидающие таймеры – это объекты ядра, предназначенные для отсчета интервалов времени. Таймеры эти могут работать как в непрерывном, так и основном для них режиме – режиме запуска и доступны в одном из режимов электропитания. Основное их достоинство, благодаря столь низкому уровню – возможность работы даже в спящем режиме. При этом не следует забывать, что ОС Windows не realtime и ожидать от них высокой точности (стабильности) не стоит, но для нашей-то задачи это не особо важно. Как проверить доступные состояния электропитания? Наберите в командной строке 'powercfg (см. рисунок 2).


Рис. 2. Проверка поддерживаемых режимов

При этом в настройках электропитания ОС должно быть:
  1. Активировано использование спящего режима.
  2. Активирован, так называемый, таймер пробуждения (который, по-умолчанию, в портативных компьютерах деактивирован).
Мало того, по-умолчанию на вкладке настроек схемы электропитания эти таймеры не показываются. О чем подробнее можно почитать на сайте самого Microsoft http://technet.microsoft.com/ru-ru/library/ff977084.aspx [1]. В семерке с этим проще, но и там аналогично эти таймеры отключены. Но сначала немного о самих режимах сна и гибернации…

Краткий экскурс…

Чем отличаются режимы* сна и гибернации? Спящий режим (см. рисунок 3) – это состояние, в котором компьютер (ПК, ноутбук или нетбук) находится в режиме пониженного энергопотребления. В этом состоянии все запущенные приложения, программы и текущее состояние рабочего стола находятся в оперативной памяти, что позволяет возобновить работу с того места, где она была прервана, причем гораздо быстрее, как если бы вы просто выключили компьютер, а потом включили.

Рис. 3. Общее меню перехода в спящий режим

Гибернация (Hibernate) – все тоже самое, но с теми отличиями, что состояние рабочего стола, запущенных приложений и документов сохраняется на винчестер (под Windows в файл hiberfil.sys), после чего компьютер выключается. Именно поэтому данный режим работает медленнее и больше предназначен для мобильных систем: ноутбуков, нетбуков.
* Для десктопных ПК есть еще гибридный спящий режим, сочетающий в себе как спящий, так и режим гибернации. При этом, все открытые документы и приложения сохраняются и в памяти и на жестком диске, после чего компьютер переводится в режим пониженного потребления электроэнергии. Следует обратить ваше внимание, что в гибридном режиме компьютер не должен обесточиваться.
Как включается спящий режим?

Для включения спящего режима в меню выключения компьютера необходимо нажать клавишу ‘Shift’. По-умолчанию этот режим требуется предварительно активировать, для чего по правой кнопке мыши заходим в настройки рабочего стола и переходим на вкладку "Заставка" (см. рисунок 4).

Рис. 4. Настройка режимов энергопитания

На данной вкладке внизу справа нажмите кнопку "Питание" для открытия окна свойств электропитания (см. рисунок 5).


Рис. 5. Окно свойств электропитания

Теперь переходим на внутреннюю вкладку "Спящий режим" (см. рисунок 6).


Рис. 6. Активация спящего режима

И активируем галочку "Разрешить использование спящего режима", после чего жмем кнопку "Применить". В дальнейшем мы будем работать именно со спящим режимом. Все, подготовка завершена.

Предпосылки программной реализации будильника

Для того, чтобы Ваш ноутбук а-ля компьютер просыпался тогда, когда необходимо, зайдите в: “Панель управления / Электропитание / Настройка плана электропитания / Изменить дополнительные параметры питания / Сон” и в открывшемся окне выберите ”Разрешить таймеры пробуждения” (см. рисунок 7).


Рис. 7. Настройка ОС Windows 7. Активация таймеров пробуждения

К чему ведь клоним? Уж не разбирали насколько глубоко они лезут в биос и используют-ли биос вообще, но факт остается фактом – данная фича при активации этих "таймеров пробуждения" выводит ПК из состояния сна. Что нам потребуется для работы с этими таймерами? Обратимся к MSDN. Всю черную работу будут выполнять пять WinAPI-шных функциий [2…7]: CreateWaitableTimer(), SetWaitableTimer(), WaitForSingleObject(), MciSendString(), CancelWaitableTimer(). Синтаксис вызова функции создания таймера CreateWaitableTimer() следующий:
HANDLE WINAPI CreateWaitableTimer(
  _In_opt_  LPSECURITY_ATTRIBUTES lpTimerAttributes,
  _In_      BOOL bManualReset,
  _In_opt_  LPCTSTR lpTimerName
);
где: lpTimerAttributes –  указатель на структуру SECURITY_ATTRIBUTES
http://msdn.microsoft.com/en-us/library/windows/desktop/aa379560(v=vs.85).aspx, определяющую дескриптор безопасности для нового объекта таймера (можно задать nil), bManualReset – тип таймера (с ручным сбросом или автосбросом), lpTimerName – имя объекта таймера.

Синтаксис вызова функции задания времени выдержки таймера SetWaitableTimer() следующий:
BOOL WINAPI SetWaitableTimer(
  _In_      HANDLE hTimer,
  _In_      const LARGE_INTEGER *pDueTime,
  _In_      LONG lPeriod,
  _In_opt_  PTIMERAPCROUTINE pfnCompletionRoutine,
  _In_opt_  LPVOID lpArgToCompletionRoutine,
  _In_      BOOL fResume
);
где: hTimer – дескриптор таймера, pDueTime – абсолютное или относительное время (первое определяет момент первого запуска и является величиной положительной, второе является отрицательной, выраженной в 100 наносекундных интервалах), lPeriod – режим работы и период повторения срабатываний таймера в миллисекундах (если равен нулю, таймер сработает однократно). Параметр 'pfnCompletionRoutine’ – указатель на необязательную функцию асинхронного вызова. Параметр lpArgToCompletionRoutine' передает произвольный аргумент, например указатель на объект или структуру, а 'bResume’ выводит компьютер из спящего состояния по срабатыванию таймера.

Синтаксис вызова функции WaitForSingleObject() следующий:
DWORD WINAPI WaitForSingleObject(
  _In_  HANDLE hHandle,
  _In_  DWORD dwMilliseconds
);
где: hObject – идентифицирует объект синхронизации, сигнала от которого нужно дождаться, dwTimeout – время ожидания в миллисекундах.

Синтаксис вызова функции перевода таймера в неактивное состояние CancelWaitableTimer() следующий:
BOOL WINAPI CancelWaitableTimer(
  _In_  HANDLE hTimer
);
где: hTimer – дескриптор таймера, полученный при создании таймера CreateWaitableTimerW().

Но какой же будильник без мелодии пробуждения? Как заставить звучать наше приложение? На помощь приходит интерфейс управления устройствами MCI (Media Control Interface), позволяющий программам под Windows работать с различными устройствами мультимедиа, в том числе и воспроизвести аудио. Конкретный набор команд соответствует каждому устройству свой. Нас интересует синтаксис вызова функции команд строк MciSendString():
MCIERROR mciSendString(
  LPCTSTR lpszCommand,
  LPTSTR lpszReturnString,
  UINT cchReturn,
  HANDLE hwndCallback
);
где: lpszCommand – команда в виде [команда][устройство][параметры], lpszReturnString – буфер для получения информации о результате, cchReturn – размер буфера, hwndCallback – каллбэк на окно отклика.

Последовательность действий в алгоритме видится следующей:
  1. Задается время будильника в виде количества тиков 100 наносекундных интервалов. Сколько таких интервалов в 1 секунде? Посчитаем. Одна секунда равна 1000 мс, тогда количество таких интервалов равно 1000 * 10^-3 / 100 * 10^-9 = 10 миллионов, а при задании количества миллисекунд их будет 10 000. Причем положительные значения будут равны абсолютному времени, а отрицательные равны относительному, т.е. от начала текущего момента.
  2. Задается длительность мелодии будильника в миллисекундах.
  3. Задается путь к аудиофайлу и его имя.
  4. Через функцию CreateWaitableTimerW() создаем таймер, работающий в ждущем режиме.
  5. Через функцию SetWaitableTimer() устанавливаем длительность таймера.
  6. Полученный дескриптор из последней функции используем для задания интервала задержки через функцию WaitForSingleObject() с флагом ожидания выполнения INFINITE.
  7. Воспроизводим заданную мелодию через функцию MCISendString().
  8. Переводим таймер в неактивное состояние через фукнцию CancelWaitableTimer(). Последнее, при закрытии приложения, является необязательным.
  9. Запускаем спящий режим.
Таким образом, уже можем сформировать основные требования к нашему будильнику:
  1. Возможность работы при активации аппаратных таймеров пробуждения.
  2. Возможность задания пользователем времени пробуждения, длительности воспроизведения и медиафайла для воспроизведения.
  3. Реализация приложения на WinAPI в виде консольной утилиты с поддержкой параметров командной строки.
  4. Открытые исходники.
Практика. Разработка ПО и средства отладки

Итак, не растекаясь мысли по древу, приступим к основной задаче. Для работы нам понадобится следующее:
  1. Бесплатная IDE среда разработки – TurboDelphi-Lite портабле (флешечная версия) [8];
  2. Наличие аудиокарты.
Ввиду ограниченности места в журнале рассмотрим только основные моменты реализации. Прежде всего, запустим IDE среду Delphi и создадим пустой проект (см. рисунок 8). Разработку осуществим на WinAPI в консоли без всяких форм.

Рис. 8. IDE среда TurboDelphi-Lite. Создание приложения

Создадим приложение, которое будет вести отсчет времени до пробуждения согласно вышеозначенному алгоритму. Код приложения-будильника будет минималистичным и простым:
program to_sleep_alarm;

{$APPTYPE CONSOLE}
uses windows, sysutils, classes, mmsystem;


function CreateWaitableTimerW(lpTimerAttributes: PSecurityAttributes;
                              bManualReset: BOOL;
                              lpTimerName: PWideChar): THandle; stdcall;
                              external 'kernel32.dll' name 'CreateWaitableTimerW';

function SetWaitableTimer(hTimer: THandle;
                          var lpDueTime: TLargeInteger;
                          lPeriod: Longint;
                          pfnCompletionRoutine: TFNTimerAPCRoutine;
                          lpArgToCompletionRoutine: Pointer;
                          fResume: BOOL): BOOL; stdcall;
                          external 'kernel32.dll' name 'SetWaitableTimer';

const INFINITE = $FFFFFFFF;
var no, musik: string;
    wnd: hwnd;
    tmr : TLargeInteger;
    tmr2: TFileTime;
    signal, dlit: integer;
begin
 // считываем и проверяем первый параметр командной строки
 no:= paramstr(1);
 if then exit;

 if or  then begin
  writeln('');
  writeln('========================================');
  writeln('HELP console SLEEP-ALARM');
  writeln('http://raxp.radioliga.com');
  writeln('========================================');
  writeln('');
  writeln('Startup options:');
  writeln('to_sleep_alarm [timer] [duration_musik] [path_musik]');
  writeln('');
  writeln('Timer, ms:');
  writeln('negative values = relative time');
  writeln('positive values = absolute time');
  writeln('');
  writeln('The remaining parameters:');
  writeln('- duration_musik, ms (default 1000)');
  writeln('- path_musik, MCI MP3/WAV/OGG (default current dir)');
  writeln('========================================');
  exit
 end;

 // время будильника из 1-го параметра командной строки
 // кол-во 100 нс интервалов
 // 1000 мс = 1000 * 10^-3 / 100 * 10^-9 = 1000 * 10 000
 // положительные значения = абсолютному времени
 // отрицательные = относительному
 tmr := 10000 * strtointdef(paramstr(1), 1000);

 // длительность будильника из 2-го параметра командной строки
 dlit:= strtointdef(paramstr(2), 1000);

 // мелодия будильника из 3-го параметра командной строки
 musik:= paramstr(3);

 // создаем таймер, работающий в ждущем режиме-
 wnd:= CreateWaitableTimerW(nil, true, 'SLEEP-ALARM');
 // устанавливаем длительность таймера-
 SetWaitableTimer(wnd, tmr, 0, nil, nil, true);
 signal:= WaitForSingleObject(wnd, INFINITE);

 // воспроизводим заданную мелодию-
 MCISendString(PChar('play + musik), nil, 0, 0);
 sleep(dlit); // ожидаем заданное время
 // деактивируем задачу и завершаем работу
 CancelWaitableTimer(wnd);
end.
После компиляции тестового приложения по клавише <F9> и задания в консоли команды мы увидим окно краткой справки по работе с будильником (см. рисунок 9).

Рис. 9. Вызов подсказки по работе с будильником из консоли

Параметры командной строки. Управляем будильником

Параметры управления следующие:
to_sleep_alarm [timer] [duration_musik] [path_musik]
где: timer, ms – отрицательные значения задают относительное время, положительные – абсолютное; duration_musik, ms – время ожидания до завершения приложения (определяет длительность звучания мелодии); path_musik – путь-имя к медиа-файлу. 

Как быстро управлять приложением? Откройте блокнот и напишите следующую команду для запуска нашей утилиты:
%cd%to_sleep_alarm -5000 12000 nw.mp3
Написали? Теперь сохраните в batch-файл с именем ‘test.bat’. Что произойдет при выполнении данного скрипта? Тут мы командой %cd% задаем путь к текущей директории и, через пробел, параметры командной строки, а именно: время отсчета будильника равное 5000 мс (знак минус определяет, что оно будет относительным от момента начала сна), длительность воспроизведения мелодии равное 12000 мс (задержка до закрытия приложения) и путь-имя аудиофайла, который служит в качестве мелодии будильника. Формат данного аудиофайла может быть как MP3, WAV или OGG. Впрочем, сие не столь важно, поскольку задачу воспроизведения и поддержки мы возложили на стандартный программный интерфейс MCI от Windows. Таким образом, если в системе присутствует кодек на заданный формат, то будет и поддержка его воспроизведения. Возможно также задание в качестве медиафайлов – видеофайла.

Как быстро перейти в спящий режим?

Тут все просто. Достаточно написать следующий batch-файл, а еще лучше просто создать по правой кнопке мыши новый ярлык на рабочем столе со следующей командной строкой управления энергопитанием:
rundll32.exe powrprof.dll,SetSuspendState 0,1,0
После чего назначить на ярлык любимую комбинацию “горячих клавиш” и вызывать при необходимости (см. рисунок 10…12).

Рис. 10. Создание нового ярлыка на рабочем столе

Рис. 11. Записываем команду для перехода в “спящий” режим

Рис. 12. Назначаем кнопки быстрого вызова (“горячую комбинацию” клавиш)

Послесловие

Обратите внимание, если у вас не работает приложение, то необходимо проверить не включен-ли гибридный спящий режим (смените его на спящий) и активирован-ли у вас в настройках электропитания пункт «Разрешить таймеры пробуждения».

Полные исходные тексты и ресурсы проекта (файл alarm-sleep.zip) вы можете загрузить с сайта автора http://raxp.radioliga.com. Если тема представляет для вас интерес – пишите, задавайте вопросы. Приятных вам снов!

Ресурсы
  1. О параметрах схемы управления питанием http://technet.microsoft.com/ru-ru/library/ff977084.aspx
  2. Джеффри Рихтер, Кристоф Назар. Windows via C/C++. Программирование на языке Visual C++. – Питер, Русская Редакция, 896 с.
  3. MSDN. CreateWaitableTimer function http://msdn.microsoft.com/en-us/library/windows/desktop/ms682492(v=vs.85).aspx
  4. MSDN. SetWaitableTimer function http://msdn.microsoft.com/ru-RU/library/windows/desktop/ms686289(v=vs.85).aspx
  5. MSDN. WaitForSingleObject function http://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx
  6. MSDN. MciSendString function http://msdn.microsoft.com/en-us/library/windows/desktop/dd757161(v=vs.85).aspx
  7. MSDN. CancelWaitableTimer function http://msdn.microsoft.com/en-us/library/windows/desktop/ms681985(v=vs.85).aspx
  8. IDE среда разработки TurboDelphi-Lite портабле http://www.andyaska.com/?act=download&mode=detail&id=34
  9. Исходники и компиляция тестового будильника из спящего режима http://raxp.radioliga.com/cnt/s.php?p=alarm-sleep.zip
Авторский материал из ж."Магия ПК", 2013, №9.

Комментариев нет:

Отправить комментарий

В комментариях уважайте собеседника, внимательно читайте посты и не додумывайте. Просьбы и предложения из разряда: «можно ваш Skype/Viber/телефон», «напишите мне в vk/FB», а также другие им подобные — игнорируются. Выход новых версий ПО, внешняя ссылка, переставшая работать с течением времени и т.п. не является основанием для претензий. Желающие спокойно подискутировать и высказаться — Welcome. Желающие спонсировать блог — Donate. Нарушение этих простых правил ведет к бану и удалению комментариев без предупреждения.