6 марта 2013

Беспроводные коммуникации «умного дома»

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


На фото выше представлен работающий прототип «Панели управления» моим «умным» домом.
Лирическое отступление #1: Как все начиналось (можно пропустить, но кому-то может оказаться полезным).

Первоначальная философия моего «умного дома» была следующей: каждое созданное устройство сделано для достижения какой-то определенной цели и оно должно работать самостоятельно.
Так дома появились устройства типа:

В доме используются не только самодельные, но и покупные «умные» устройства. Одним из таких очень удачных устройств является хронотермостат:


Термостат работает в двух режимах:
  • «Ночной» — температура пониже, чтобы было комфортнее спать (и экономить газ).
  • «Дневной» — температура выше и более комфортная для бодрствования.

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

Но так как мой рабочий день не нормирован, после нескольких ранних возвращений выяснилось, что возвращаться в прохладный дом с мороза днем как-то не очень приятно.

С этого момента началась эпопея с беспроводными коммуникациями (можно было, конечно, опутать весь дом «витой парой», но это не наш метод).

Выбор беспроводных модулей был достаточно спонтанный, но после нескольких месяцев эксплуатации он вполне оправдал себя.

Были приобретены дешевые трансиверы на 2.4ГГц nRF24L01+:


По характеристикам заявлено, что дальность до 100м (в прямой видимости) и скорость передачи данных до 2Мбит. Возможно использование любого из 126 доступных частотных каналов. Очень достойно для такой «крохи».

На всякий случай для наиболее «критичных» модулей нашлись «усиленные» версии:


Эти модули полностью совместимы с «обычными», но дополнительно оснащены усилителем мощности и внешней антенной.
Кстати, они пока так и лежат без дела — оказалось, что в моем случае достаточно «обычных» модулей.

Модули подключаются к МК через SPI.
Особенность модулей такова, что им для питания требуется 3.3В (т.е. необходимо им обеспечить индивидуальное питание, если вся остальная схема питается, например, от 5В). Приятной новостью является то, что эти RF-ки толерантны к 5В-сигналам (не требуется использовать схему согласования уровней).

Лирическое отступление #2: Первые опыты.
Первоначально для работы с беспроводными модулями использовалась библиотека Mirf.

Для устранения проблемы «прохладный дом» (см. Лирическое отступление #1) был создан «принудительный» термостат.
Модуль из себя представляет небольшую коробочку, которая подключается между газовым котлом и хронотермостатом:


Модуль оборудован 2 релюшками, которые обеспечивают необходимую коммутацию, собственным датчиком температуры (DS18B20) и 3 светодиодами для индикации режима работы.

Логика простая: если у «принудительного» термостата выбран автоматический режим работы — он изображает из себя «кусок кабеля» и никак не влияет на взаимодействие котла и хронотермостата. А вот если его перевели в режим «ручной» работы — хронотермостат отключается от котла и уже не участвует в работе, за температуру дома становится ответственным «принудительный» термостат.
Модуль обладает 2 параметрами:
  • Режим работы (0 — авто, 1 — ручной).
  • Заданная температура.

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

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

Но чтобы им управлять, нужно как минимум еще одно.

Выбор пал на плату gBoard (атмега328, gsm на базе sim900, интерфейс для nrf24l01 (используется аппаратный SPI), SD, кучка аналоговых пинов и проч.):


Слот под сим-карту расположен на обратной стороне платы (фотографировать не стал — там больше ничего интересного нет).

Опять же с использованием той же библиотеки для nrf24l01 (Mirf) был написан скетч.
Теперь появилась возможность через СМС запросить текущую температуру дома, задать желаемую температуру и перевести «принудительный» термостат из одного режима в другой.

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

Аналогичным образом модуль для ИК-управления светом был оснащен беспроводным модулем и тоже стал управляться через СМС (опять же, этот модуль стал еще одним «датчиком» в системе — температура/влажность/состояние света).

Через некоторое время почти все «поделки» обзавелись беспроводным интерфейсом и стали как минимум «датчиками», а как максимум — исполнительными устройствами с возможностью удаленного управления.

При этом все устройства остались верны основному принципу, обозначенному выше: каждое созданное устройство сделано для достижения какой-то определенной цели и оно должно работать самостоятельно.


И вроде бы как все работает и все хорошо, но как-то не шибко удобно — только через СМС, да еще довольно сложно было внедрять новые фичи. Контроль исполнения осуществлялся или еще одной СМС с запросом на текущее значение «сенсора» или заглядыванием в систему видеонаблюдения (чтобы увидеть, например, включенный свет).

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

Формат структуры для передачи данных был не оптимален (для моего случая). Эту версию формата даже приводить не хочется.

Понятно, что необходимо как-то модифицировать (предпосылка #1).

Естественно, появилось желание получать информацию и управлять еще и через LAN. Была приобретена платка iBoard (атмега328, LAN на базе Wiznet W5100, интерфейс для nrf24l01, SD, кучка аналоговых пинов и проч.):


И все вроде бы все как на gBoard, но оказалось, что беспроводной модуль разведен совсем не так, как ожидалось. Аппаратный SPI в этой плате используется для LAN и SD, а вот для беспроводного модуля предлагалось реализовать этот интерфейс самостоятельно. Сделано это было скорее всего для того, чтобы можно было «одновременно» работать как с LAN, так и с RF.

Библиотека Mirf на этой плате с такой коммутацией модуля работать отказалась категорически. Писать свою библиотеку для взаимодействия с nrf24l01 в мои планы совсем не входило.

Очевидно, что надо что-то делать, иначе хорошая плата пойдет «в стол» до лучших времен (предпосылка #2).

Была обнаружена чудесная библиотека RF24 (автор maniacbug, Сиэтл, США) и еще более чудесный форк iBoardRF24 сделанный Андреем Карповым (Киев, Украина).

Библиотеки замечательным образом заработали на всех имеющихся устройствах (тестовые примеры), но вот устройства с разными библиотеками (Mirf и RF24) между собой не могут общаться (предпосылка #3).

Стало очевидно, что настал-таки тот самый момент, когда надо все переделать.
Далее будут приводиться куски кода (Arduino IDE) для иллюстрации текущего решения. Полный листинг не привожу, чтобы оставить простор для творчества.

Имея за плечами уже солидный накопленный опыт, взял из структуры передаваемых данных все хорошее и учел имеющиеся недостатки пришел к тому, что лучше использовать следующую структуру «пакета» данных:
// структура для передачи значений
typedef struct{         
  int SensorID;        // идентификатор датчика
  int CommandTo;       // команда модулю номер ...
  int Command;         // команда
                            // 0 - нет команды или ответ
                            // 1 - получить значение
                            // 2 - установить значение
  int ParamID;         // идентификатор параметра 
  float ParamValue;    // значение параметра
  boolean Status;      // статус 0 - ошибка, 1 - ок
  char Comment[16];    // комментарий
}
Message;

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

Внутри же каждого «датчика» необходимо иметь структуры для параметров:
// структура для параметров и значений
typedef struct{
  float Value;         // значение 
  boolean Status;      // статус - 0-ошибка (false), 1-ок (true)
  char Note[16];       // комментарий
} 
Parameter;

В структуру заведено три параметра. Ну со «значением» все понятно, поле «Note» используется для того, чтобы дать какое-то внятное (но краткое) объяснение, что же это за датчик и, возможно, единица измерения.
Status заведен больше для контроля и возможной гибкости в дальнейшем.
Сейчас это используется, например, если не удалось по каким-то причинам считать данные с какого-то датчика, то система может спокойно обработать эту ситуацию и передать 0, обозначив проблему.

Видно, что вторая структура полностью повторяет «конец» структуры передаваемого пакета.

Формат передаваемого «пакета» абсолютно идентичен как для «master» (тот, кто рассылает команды и собирает данные), так и для «slave» (тот, кто принимает команды и отчитывается о проделанной работе). Более того, при таком подходе master и slave — достаточно условные обозначения. Модули реально общаются «на равных» и запросы с ответами могут идти в обе стороны.

Теперь можно уже приступать к описанию конкретного модуля. Возьмем какой-нибудь гипотетический модуль с 3 параметрами. Кодироваться это будет следующим образом:
// описание параметров модуля
#define SID 100                        // идентификатор датчика
#define NumSensors 3                   // количество сенсоров (и еще одно обязательное занчение - имя датчика)

Parameter MySensors[NumSensors+1] = {    // описание датчиков (и первичная инициализация)
  NumSensors,1,"Sensor 100: LED",        // в поле "комментарий" указываем пояснительную информацию о датчике и количество сенсоров, 1 в статусе означает "отвечаем на запросы"
  0,0,"value A0",
  0,0,"counter",
  0,0,"set_value",
};
Message sensor; 

Обратите внимание, что не смотря на то, что параметров 3 — объявляется структура из 4 элементов. Для себя я принял, что в «нулевом» элементе я буду всегда кодировать информацию о самом «модуле» — количество его датчиков, давать информацию о том, что датчик может принимать команды или он только «датчик» и текстовое описание датчика.
Сейчас думаю, что при описании датчика параметр Status можно использовать для обозначения — можно ли этот параметр менять (т.е. «исполнительный механизм») или нельзя (просто «значение датчика»).
Или ввести для этого еще один параметр?

С данными разобрались, перейдем к функциям:
boolean sendMasterMessage(int From, int To, int Command, int ParamID, float Value, char* Comment, Message* answer);

Так объявлена функция, с помощью которой отправляется команда.
Возвращает «true», если пришел правильный ответ или «false», если постигла неудача.
Входные параметры: от какого модуля идет команда, какому модулю, команда (читаем или устанавливаем), какой параметр, значение, комментарий и ссылка на структуру для ответа.
Параметр «комментарий» тут является больше «закладкой на будущее» для того, чтобы, например, можно было менять имена модулей и названия параметров.

Результат функции определяется по следующим правилам:
  1. ответ должен быть получен от того датчика, к которому обращались
  2. ответ должен предназначаться тому мастеру, который инициировал запрос
  3. в ответе должен быть ParamID тот, что участвовал в передаче

Таким образом, чтобы узнать значение первого датчика с нашего гипотетического сенсора, достаточно на мастере (пусть его SID будет 900) вызвать функцию следующим образом:
  ...
  Message answer;
  ...
  if(sendMasterMessage(900, 100, 1, 1, 0, "", answer)) {
    Serial.println(answer.ParamValue);
  }
  else {
    Serial.println("No answer");
  }
  ...


Соответственно на «принимающей» стороне в основном цикле должен быть примерно такой код:
  // если получена команда
  if (radio.available()) {
    bool done = false;
    while (!done)
    {
      done = radio.read( &sensor, sizeof(sensor) );
      // если команда этому датчику - обрабатываем
      if (sensor.CommandTo == SID) {
        // исполнить команду (от кого, команда, парметр, комментарий)
        doCommand(sensor.SensorID, sensor.Command, sensor.ParamID, sensor.ParamValue, sensor.Status, sensor.Comment);
      }
    }
  }


Функция doCommand может быть примерно такой:
void doCommand(int From, int Command, int ParamID, float ParamValue, boolean Status, char* Comment) {
  switch (Command) {
    case 0:
      // ничего не делаем 
      break;
    case 1:
      // читаем и отправляем назад
      sendSlaveMessage(From, ParamID);
      break;
    case 2:
      // устанавливаем
      setValue(From, ParamID, ParamValue, Comment);
      break;
    default:
      break; 
  }
  // ответим "командующему"
  sendSlaveMessage(From, ParamID);

  return;
}


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

Таким образом, логика работы получается простая:
  • Master инициирует адресный запрос к модулю и требует прочитать (или установить) значение определенного параметра.
  • Slave принимает эту команду (и если она адресована именно ему) и выполняет требуемое, о чем «отчитывается» в ответном сообщении.
  • Master анализирует принятый пакет и если удовлетворен результатом — занимается дальше своими делами или делает повторный запрос (нужное количество раз).

Код для gBoard тоже был переписан соответствующим образом и теперь взаимодействие через СМС выглядит примерно так (часть скриншота с моего телефона):

Первой СМС я запрашиваю температуру восточного датчика у «погодного» модуля, второй СМС — устанавливаю желаемую температуру «принудительному» термостату. В ответ я получаю измеренное значение или ответ об успешно установленном значении (в конце сообщения есть «ОК!»).

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

На этом «адресный» обмен данными описывать закончу.

Но ведь в домашней системе есть и другие модули, которые являются примитивными «датчиками». Типичный образец такого модуля — датчик температуры и влажности, который, возможно, работает от батарейки и ему свои ресурсы надо беречь и «спать крепким сном» и просыпаться исключительно для того, чтобы быстренько отправить данные о своем состоянии и снова «спать».
Для него режим «запрос-ответ» не подходит (придется держать как минимум RF-модуль в «бодрствующем» состоянии, что негативно скажется на времени автономной работы).

Тогда придумал, что можно устраивать «флуд» в эфире: датчики могут на регулярной основе отправлять значения своих параметров (всех или только некоторых — зависит от конкретной реализации) для всех (тогда параметр To (кому) принимает значение 0 (т.е. «всем»)).

Получается эдакий «мультикаст» — модуль проснулся, выдал в эфир свое состояние (и ему не важно, услышал кто-то или нет) и снова уснул.

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

А теперь осталось только данные собрать воедино. Тут-то и пригодилась плата iBoard (еще раз приведу ее фото, с уже установленным RF-модулем):


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

В пакете данных для веб-сервера передается «ключ» (чтобы сервер понимал, что это «свои») и полный принятый пакет (еще раз повторю, что в пакете приходят следующие ключевые данные: SID модуля, ID параметра, значение этого параметра, комментарий (имя параметра и/или единица измерения)).
Если же в принятом пакете ParamID равен нулю, то в значении параметра мы имеем количество «сенсоров» у модуля, а в «комментарии» — имя модуля.

На веб-сервере в автоматическом режиме формируются записи в 2 табличках: sensor_list и sensor_value (из названий, думаю, все понятно).

Примененный подход позволяет совершенно безболезненно добавлять модули в систему и не исправлять код для iBoard — в базу данных новые значения попадут по отработанной схеме.

В качестве веб-сервера использовал то, что «было под рукой» (Synology DS411+II) — в пару кликов был поднят веб-сервер с php и mySQL. В случае, если «под рукой» нет ничего подходящего и не хочется ради этого держать включенный комп — можно воспользоваться сторонним хостингом, приобрести что-то типа этого (приятно и полезно) или, например, сервисом cosm.com

В зависимости от выбора «хранилища», код для МК с LAN-модулем будет изменяться минимальным образом (на cosm.com для ардуинки есть своя библиотека и удобный режим отладки — использовать очень просто).

Данные собрали (а в случае использования cosm.com — уже и отобразили на графиках).

В своем проекте для отображения я использовал Google Charts. Получается примерно так (только погодные параметры):


Специально привожу скриншот, а не даю ссылку на страницу — NAS, думаю, не выдержит нагрузки с хабра.
Данные с датчиков «зашумлены», надо бы применить фильтр Калмана, но руки пока не дошли до этого.

Собственно, со сбором и отображением данных — тоже закончил.

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

Вы можете спросить: «При чем же та картинка, с которой начался пост?»

А это еще одна плата для быстрого прототипирования — iBoardPro (построена уже на mega2560, LAN, RTC, SD, RF-интерфейс, 40-пиновый разъем для подключения TFT дисплеев с тачскрином и другими «плюшками»):


На фото хорошо виден дисплей TFT 2.4" 320*240 с резистивным тачскрином ITDB02-2.4E. Выглядит хорошо, но вот для работы пальцами — мелковат, да и тексты хотелось бы покрупнее (буду смотреть в сторону 5 или даже 7-дюймовых дисплеев).

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

Небольшое видео, демонстрирующее текущую работу прототипа:


Логика работы следующая:
  • Есть «главные» экраны — на них попадают «сводные» данные («Погода» — зеленый, «Дом» — желтый)
  • Есть «субэкраны» — на них отображаются данные с датчиков (на видео это продемонстрировано для «желтой» закладки — «домашние параметры»: температура и влажность в двух точках дома и данные по электропотреблению).
  • «Красный» экран — это «сводка из сводок» — наиболее востребованные данные.
  • «Синий» экран — управление. В средней его части находится управление светом, в нижней — управление «принудительным» термостатом. Слева снизу красный «индикатор» — это отображение того самого параметра (о котором я писал выше), отображающего, можно ли газовому котлу в текущий момент времени работать.

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

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

«Фиолетовый» экран планируется использовать для вывода данных о состоянии системы: текущий IP-адрес, время старта, время последней синхронизации через NTP, времена последних «коннектов» (или «прослушиваний») с конкретными датчиками и т.п.

Пожалуй, что на сегодня это все, о чем хотел рассказать (и так очень длинно получилось).

Извините за качество фото — снимал на микроволновку телефон.

P.S. «Железки» приобретал в этом магазине (конечно, переплатил, но ждать долго-долго из Китая не хотелось, за это время успел заказать и получить несколько посылок из НСК). Ближе и быстрее нигде не нашел.

P.P.S. да, все эти «железки» (ну разве что кроме дисплея) можно собрать дома, но это не было целью.
Теги:nrf24l01+atmegaiBoardgBoard
Хабы: Беспроводные технологии
+69
147,1k 847
Комментарии 56
Лучшие публикации за сутки

Минуточку внимания

Разместить