Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР 128, АВГУСТ 2009 г.

Что нам стоит «умный дом» построить. Делаем фарш из микроконтроллеров и роутера

Алексей «Cluster» Авдюхин (clusterrr@clusterrr.com)

В этой статье я расскажу историю создания собственного «умного дома». Идея стара, как мир, но свой агрегат я подружил с бытовым роутером от ASUS! Это дало полноценный контроль устройствами по сети с помощью программного обеспечения для Linux. Хочешь узнать, как выжать из «железа» роутера максимум?

С чего все начиналось…

С детства у меня было желание автоматизировать в своей комнате все, что только можно. Иными словами, сделать «умный дом». И чем умнее, тем лучше. Основная цель – это дистанционно включать и выключать различные устройства: лампы, люстру, розетки и пр. Когда-то я не был знаком с микроконтроллерами и делал все через LPT-порт компьютера. Сказать, что это было неудобно – не сказать ничего. Компьютер я не выключал никогда, при каждой перезагрузке в комнате начиналась «дискотека», а при переустановке системы вообще приходилось сидеть в темноте. Изучение микроконтроллеров изменило ситуацию.  Я задумал создать устройство, которое будет выполнять те же функции, но при этом должно легко управляться с компьютера и в то же время быть независимым от него.

Решение вполне очевидно. Устройствами управляет микроконтроллер ATmega16, который связывается с компьютером через COM-порт. Для включения и выключения устройств я взял несколько электромагнитных реле. Использовать их очень просто: подаем питание, и они замыкают контакты. Думаю, тебе уже известно, что логическая единица на ноге микроконтроллерах AVR – это примерно +5 вольт, а логический ноль – соответственно, 0 вольт. Однако подключать реле напрямую нельзя. Во-первых, 5 вольт может быть мало.

Во-вторых, нельзя, чтобы через ногу микроконтроллера шел слишком большой ток. Надо использовать промежуточные реле или транзисторы; соответствующая схема приведена на рисунке. С точки зрения прошивки это самая простая часть. Записываем логическую единицу в бит соответствующей ноги (в регистре PORTx, где x – буква порта), и свет в комнате включается. Записываем ноль – выключается. Проще уж точно некуда. Теперь про связь с компьютером через COM-порт – для этого используется встроенный в микроконтроллер USART-порт.

Пример работы с ним подробно рассматривался в прошлом номере (статья «Высокий уровень программирования, пишем на Си под AVR»). Не стоит забывать о том, что должна быть возможность управлять всем и без компьютера; центральный блок должен быть самостоятельным устройством. Поэтому я также подключил к микроконтроллеру текстовый ЖК-дисплей и кучу кнопок. С помощью всего этого было решено реализовать простенький интерфейс из нескольких меню. А если уже есть дисплей, то почему не сделать возможность выводить на него произвольную информацию, получаемую с компьютера?

Правда, круто?

И я это сделал. Затем начали появляться другие идеи: ДУ приемник для управления с пульта ДУ от любого телевизора, термодатчики для отображения температуры на улице и в комнате; датчик движения для автоматического отключения света, когда меня нет. Проект очень активно развивался. Протокол для взаимодействия с компьютером усложнился. Для Windows была разработана соответствующая программа, которая командовала устройству включать/выключать люстру при нажатии «горячих» клавиш на клавиатуре, ставила в аське/мирке статус «отошел», если датчик движения долго меня не замечал, выводила текст сообщений из той же аськи/мирки на ЖК-дисплей. А также делала многие другие фишки.

Подводные камни, или Используем сеть

При всем этом начали проявляться другие траблы. «Умный дом» взаимодействовал с компьютером, но только с одним. А мне хотелось, чтобы я мог управлять всем с любого устройства по сети, будь то мобильный телефон, Nintendo DS или же просто удаленный компьютер. Первое решение, которое пришло в голову, было достаточно банальным. Я доработал программу, которая общается через COM-порт с «умным домом», так, чтобы она принимала подключения по сети, позволяя управлять всем удаленно. В итоге я вернулся к тому, с чего начинал: компьютер приходилось постоянно держать включенным.

Мною были рассмотрены различные способы работы с последовательным портом через сеть. Решение нашлось, когда я приобрел роутер ASUS WL-500gP. Дело в том, что в этом замечательном устройстве крутится Linux, а внутри роутера есть два UART-порта, которые используются на заводе для отладки. Один из них позволяет работать с системной консолью, а второй никак не используется. Ничто не мешает нам превратить UART в COM-порты, используя микросхему MAX3232. Я так и сделал. Подробнее фича рассматривалась совсем недавно, в ][ № 125 (статья Сергея Долина «Великий и могучий UART»). Итак, мы получили роутер, где есть Linux, сеть и COM-порты – идеальное устройство для моих целей, так как работает круглосуточно, не шумит и электричества кушает мало. С аппаратной точки зрения решение найдено, но как быть с софтом? И я задался вопросом программирования под роутер.

«Олеговские» прошивки

Многие роутеры от ASUS стали популярны благодаря так называемым «олеговским» прошивкам, которые разрабатывает наш соотечественник Олег (увы, фамилию свою он нигде не упоминает). Эти прошивки позволяют получить полноценный доступ к Линуксу, а на продвинутых моделях еще и устанавливать дополнительный софт. Скачать это чудо можно на официальном сайте: http://oleg.wl500g.info. Когда я впервые поставил такую прошивку на свой WL-500gP, счастью не было предела. После небольших манипуляций установка софта свелась к простому использованию менеджера пакетов «ipkg». Эта тема подробно рассматривалась в статье Step’а (][ №106, статья «Level-up для точки доступа»), поэтому буду краток. В USB-порт я воткнул флешку побольше, подключился к роутеру телнетом и выполнил следующие команды:

Установка ipkg

mount /dev/scsi/host0/bus0/target0/lun0/part1 /opt
ipkg.sh update
ipkg.sh install ipkg-opt
ipkg update

Обрати внимание, что «/dev/scsi/host0/bus0/target0/lun0/part1» - это EXT3-раздел на моей флешке. Она уже разбита и отформатирована; соответственно, у тебя путь может отличаться. Полагаю, ты уже знаешь, как использовать «fdisk» и «mke2fs»; я опущу эти инструкции. Да, и не забывай, что при этом у роутера должен быть доступ в интернет, – он будет сам качать пакеты. Чтобы монтирование производилось автоматически после загрузки, я выполнил еще несколько команд:

Автоматическое монтирование флешки

echo “#!/bin/sh” > /usr/local/sbin/post-mount
echo “mount /dev/scsi/host0/bus0/target0/lun0/part1 /opt” >> /usr/local/sbin/post-mount
chmod +x /usr/local/sbin/post-mount
flashfs save
flashfs commit
flashfs enable

Файл «post-mount» выполняется системой на автомате после монтирования дисков, а последние три строки сохраняют изменения во встроенной памяти роутера. Не забывай выполнять их, иначе рискуешь потерять данные после перезагрузки! Затем можно устанавливать пакеты из репозитория, используя простую команду «ipkg install <имя_пакета>».

Пишем программы для роутера

Но нас с тобой интересуют не чужие программы, а свои! Как же их писать? Это оказалось гораздо проще, чем я ожидал. Линукс, он и в Африке Линукс. И если ты умеешь писать программы для него, то сможешь писать их и для роутера. Разницы никакой. Что для этого нужно? Конечно, компилятор. Я компилирую сырцы прямо на роутере: долго, зато удобно. Если хочешь делать так же, то смело ставь пакет «buildroot». Делается это одной командой «ipkg install buildroot», если ipkg у тебя уже установлен и настроен. Далее становятся доступны gcc, g++, make и все стандартные библиотеки. Надеюсь, ты уже знаешь, как их использовать? Я первым делом написал простейшую программу:

Hello world

[Cluster@CLUSTER Cluster]$ cat hello.c
#include <stdio.h>

int main()
{
printf("Hello world!n");
}
[Cluster@CLUSTER Cluster]$ gcc hello.c -o hello
[Cluster@CLUSTER Cluster]$ ./hello
Hello world!

Просто, но эффектно. Осталось воплотить в жизнь мою идею. Для этого нужно написать программу, которая работает с последовательными портами и сетевыми сокетами. Алгоритм несложен. Прослушиваем TCP-порт, принимаем входящие подключения по сети. Если от любого из сетевых клиентов приходят данные, то пересылаем их на UART-порт. Если, наоборот, из последовательного порта пришли данные, то рассылаем их всем сетевым клиентам.

Последовательные порты в тамошнем Линуксе имеют имена «/dev/usb/tts/0» и «/dev/usb/tts/1». Первый, как я уже говорил, используется для системной консоли. А второй свободен, и мы можем использовать его для своих целей. Помнишь, в прошлом номере мы рассматривали подключение ЖК-дисплея к COM-порту компьютера через микроконтроллер? Это устройство легко подключить к роутеру, чтобы выводить какие-то данные на экран, и компьютер для этого уже не нужен. Я так и сделал, а затем набрал в консоли две простые команды:

Проверка порта

stty -crtscts 9600 < /dev/tts/1
echo "Hello world!" > /dev/tts/1

Первая команда – установка параметров порта. Вторая – вывод текста. Эксперимент удался, и я увидел на экране соответствующий текст. А вот как это сделать из собственной программы? Работать с портами в Линуксе мне пришлось впервые, получилось далеко не сразу, и пришлось долго экспериментировать с различными параметрами. Приведу код функции, которая открывает порт для чтения и записи:

Открываем последовательный порт

int open_uart_port()
{
int fd;
struct termios options;
fd = open(UARTPORT, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
perror("Can't open port");
exit(1);
}
tcflush(fd, TCIFLUSH);
tcgetattr(fd, &options);
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_cflag &= ~CRTSCTS;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
cfsetospeed(&options, B9600);
tcsetattr(fd, TCSANOW, &options);
fcntl(fd, F_SETFL, FNDELAY);
printf("UART (%s) port openedn", UARTPORT);
return fd;
}

Тут константа «UARTPORT» - путь к файлу-устройству, который ассоциируется с портом. В данном случае это «/dev/tts/1». Собственно функция «fopen()» открывает порт, далее просто идет изменение различных параметров. В примере используется скорость в 9600 бод, один стоповый бит; контроля передачи данных нет. Запись и чтение производятся функциями «write()» и «read()». Как работать с сокетами, наверное, многие уже знают, – для остальных приведу функцию, которая открывает порт и подготавливает его для принятия соединений:

Создаем сокет и прослушиваем его

int StartListen()
{
int sock;
int i = 1;
if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
perror("Can't create socket");
return -1;
}
bzero(&sa, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port   = htons(CCPORT);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sock, (struct sockaddr *)&sa, sizeof sa))
{
perror("Can't bind port");
close(sock);
return -1;
}
if (listen(sock, 15))
{
perror("Can't listen port");
close(sock);
return -1;
}
if (ioctl(sock, FIONBIO, &i))
{
perror("Can't set non-blocking mode");
close(sock);
return -1;
}
printf("Listening on port %un", CCPORT);
return sock;
}

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

Клиентские программы

Протокол для общения с «умным домом» стал очень сложным, он слишком заточен под мои нужды и описывать его тут нет смысла. Основную клиентскую программу я написал для Windows, используя при этом Borland Delphi. Получилась софтина, которая запускается вместе с системой и висит в трее. При этом каждые несколько секунд она подключается к серверной программе, которая запущена на роутере. Когда соединение установлено, происходит обмен данными: компьютер узнает, какие устройства присутствуют и какие включены, какая температура в комнате и т.д. Остальные функции изменились незначительно, их я описывал в начале статьи. Идей при этом не меньше, реализовать так можно очень многое.

Бонусы роутера

Если все это безобразие работает под Линуксом, значит, можно использовать множество других утилит, портированных на этот роутер. Самое очевидное – это веб-сервер! Я использую «lighttpd», он достаточно легкий и умеет все, что нужно. Его легко подружить с PHP. А что мешает написать клиент для «умного дома» на PHP? Именно так я и сделал, используя функции для работы с сокетами. Это дало возможность управлять домом с любого устройства, на котором есть веб-браузер, например, мобильного телефона. Даже не пришлось переносить код на Симбиан.
Также я воспользовался утилитой «rrdtool», – это очень интересная программа, которая позволяет заносить информацию в своеобразную базу данных и рисовать по ней графики. Мне было любопытно, и я реализовал вывод графиков с данными о температуре за прошедшие сутки, неделю и т.д. Получилось весьма симпатично. Планирую еще сделать, чтобы отслеживалось движение в комнате, и роутер присылал мне SMS, если вдруг в комнату кто-то вошел. Своеобразная самодельная сигнализация.

Кофе в постель

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

Альтернативные методы

Если ты не хочешь разбирать свой роутер, то можешь просто купить USB-COM шнурок и воткнуть его в USB-порт. В прошивке Олега уже есть драйвера для таких устройств на основе чипа «pl2303». Достаточно загрузить модули командами «insmod usbserial.o» и «insmod pl2303.o». В результате появится еще один порт «/dev/usb/tts/0».

Диск

На диске ты найдешь исходники моей программы для роутера. Используй ее в качестве примера.

Содержание
ttfb: 6.8390369415283 ms