Одним из первых моих проектов при изучении Arduino была работа с цифровым датчиком температуры DS18B20. Затем я подключил три датчика DS18B20 и отправлял показания на сайт. Не так давно приобрел датчик температуры и влажности и к конкурсу решил создать проект сервера домашней метеостанции, при обращению к которому выдаются результаты с датчиков по JSON и написать виджет к телефону Android, показывающий данные показания. Виджет выводит показания с датчиков при нахождении дома (домашняя сеть), или из любого другого места при подключении телефона к интернету.
Создание домашнего сервера метео на Arduino
Дома подключены три датчика DS18B20 и один датчик DHT11
Схема подключения следующая:
При написании программы использовались следующие библиотеки Arduino
- Ethernet - библиотека для работы с Ethernet-shield
- spi - взаимодействовать с устройствами поддерживающими SPI протокол
- onewire - взаимодействие с устройствами по протоколу 1-Wire
- dht - Arduino библиотека для работы с датчиками DHT11,DHT22
Создаем web-сервер, присваиваем ip и порт обращения 10001, и при обращении к серверу опрашиваем датчики DS18B20 и DHT11. Чтобы сервер не тратил время на опрос и поиск кодов датчиков DS18B20 я внес уже полученные коды в массив my_addr. Результат сервер отдает в формате JSON.
Вот код скетча
#include "Ethernet.h" #include "SPI.h" #include "OneWire.h" #include "DHT.h" #define DHTTYPE DHT11 // DHT 11 DHT dht(8, DHTTYPE); OneWire ds(7); // on pin 7 byte mac[] = { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }; // IP адрес, назначаемый Ethernet shield: byte ip[] = { 192, 168, 1, 111 }; EthernetServer server(10001); // коды датчиков DS18B20 byte my_addr[3][8]={{0x28,0x81,0xC4,0xBA,2,0,0,0x3B}, {0x28,0x67,0xE5,0xC7,2,0,0,0xA0}, {0x28,0xF6,0x98,0xBA,2,0,0,0x92}}; void setup() { Serial.begin(9600); Serial.println("start"); // инициализация Ethernet shield Ethernet.begin(mac, ip); // запуск сервера server.begin(); Serial.print("server ip "); Serial.println(Ethernet.localIP()); } void loop () { EthernetClient client = server.available(); if (client) { // an http request ends with a blank line boolean currentLineIsBlank = true; while (client.connected()) { if (client.available()) { char c = client.read(); if (c == '\n' && currentLineIsBlank) { client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); client.print('{'); client.print('"');client.print("meteo");client.print('"');client.println(":"); client.print('{'); for(int j=1;j<4;j++) { //Serial.print("temp");Serial.print(j);Serial.print("="); //int Temp=get_temp(j); //Serial.print(Temp/16);Serial.print(".");Serial.println(((Temp%16)*100)/16); client.print('"');client.print("temp");client.print(j);client.print('"');client.print(":"); client.print('"');client.print(Temp/16); client.print("."); client.print(((Temp%16)*100)/16);client.print('"');client.print(','); } float h = dht.readHumidity(); float t = dht.readTemperature(); //Serial.print('"Humidity4":'); Serial.println(h);Serial.print(" %\t"); //Serial.print('"Temp4":'); Serial.print(t);Serial.println(" *C"); client.print('"');client.print("temp4");client.print('"');client.print(":"); client.print('"');client.print(t);client.print('"');client.print(','); client.print('"');client.print("humidity4");client.print('"');client.print(":"); client.print('"');client.print(h);client.print('"'); client.println("}"); client.print("}"); break; } if (c == '\n') { currentLineIsBlank = true; } else if (c != '\r') { currentLineIsBlank = false; } } } delay(1); client.stop(); } } // получение температуры датчика int get_temp(int nn) { byte i; byte present = 0; byte data[12]; byte addr[8]; int Temp; ds.reset(); ds.select(my_addr[nn-1]); ds.write(0x44,1); // start conversion, with parasite power on at the end delay(1000); // maybe 750ms is enough, maybe not // we might do a ds.depower() here, but the reset will take care of it. present = ds.reset(); ds.select(my_addr[nn-1]); ds.write(0xBE); // Read Scratchpad for ( i = 0; i < 9; i++) { // we need 9 bytes data[i] = ds.read(); } Temp=(data[1]<<8)+data[0]; Temp=Temp; return Temp; }
Теперь пишем виджет на Android, чтобы на телефоне видеть показания датчиков, находясь в любом месте при наличии соединения с интернет.
Виджеты — это маленькие приложения, которые могут быть размещены на рабочем столе вашего Android-устрйства. Виджет периодически получает новые данные и обновляет свой вид.
Для создания виджета вам необходимо:
1. Создать XML-layout файл со слоем, в котором описывается внешний вид виджета.
2. Создать XML файл метаданных, в котором задаются различные характеристики виджета:
- layout-файл (из п.1.), чтобы виджет знал, как он будет выглядеть
- размер виджета, чтобы виджет знал, сколько места он должен занять на экране
- интервал обновления, чтобы система знала, как часто ей надо будет обновлять виджет
3. Создать BroadcastReceiver, который будет использован для обновления виджетов. Этот приёмник расширяет AppWidgetProvider, который обеспечивает жизненный цикл виджета.
4. Изменения в файле AndroidManifest.xml
Рассматривать программирование на Android я здесь не буду, займет слишком много места. Проблем при программировании обнаружилось много, сначала делал для Android 2.1, этот код для Android 2.3 уже давал ошибки при работе со строками, измененный код не пошел для Android 3.2 - изменились методы получения сетевых данных (работа стала возможна только в отдельном потоке), для Android 4.0 проверить не на чем, эмулятор постоянно виснет, а устройств нет. В общем времени для написания программы ушло больше недели. Для тех кому интересно в конце топика выложена ссылка на файлы данного проекта проекта для среды Eclipse.
При запуске виджета он пытается соединиться с внутренней сетью (192.168.1.111:10001 на тот случай, если я нахожусь дома), при неудаче с адресом в сети интернет хх.хх.хх.хх:10001
Если соединение удачно - парсим JSON-ответ выводим данные виджете и обновляем виджет
При неудаче - сообщение в виджет об отсутствии соединения
При нажатии на иконку происходит обновление данных.
У меня дома интернет через ADSL-модем, необходимо открыть порт 10001
Архив с файлами проекта для среды Eclipse находится в файле ArduinoMeteo.rar
Из ближайших планов модернизации проекта:
- подключение дополнительных датчиков (например BMP085 (уже заказан и в пути)),
- получение данных сервера метеостанции ROS и выдача голосовых сообщений по запросу голосом (см. проект) и по будильнику.
- придание программе на Android товарного вида (настройки и пр.) (это уже когда появится время).
Продолжение статьи:
Cервер домашней метеостанции на Arduino + Виджет на Android. Добавление датчика BMP085
Cервер домашней метеостанции на Arduino - виджет для OS X
Прикрепленные файлы:
- ArduinoMeteo.rar (686 Кб)
- ArduinoMeteo.rar (686 Кб)
- ArduinoWeatherServ.zip (2 Кб)
Комментарии (25) | Я собрал (0) | Подписаться
Для добавления Вашей сборки необходима регистрация
[Автор]
[Автор]
Что касается динамического адреса, проблема скорее надумана, ибо почти все модели роутеров могут работать с DDNS. А некоторые брэнды типа ASUS и вовсе предоставляют собственный сервис для своей продукции. Если это не роутер, а типичный 3G-модем и прочее опсосное, то рациональнее будет самому ардуино-серверу выгружать данные на сайт.
[Автор]
Касательно голосового оповещения, можно конечно использовать для озвучки тот же http://translate.google.ru/translate_tts. Однако, ввиду тенденции сокращения своих сервисов гуглом (вон очередной ридер и войс для блэкберри закрыли), все же рациональнее будет воспользоваться любыми локальными TTS для синтеза, движок Nikolay неплох.
P.S. Впрочем, если набор фраз для озвучки температуры и прочего ограничен, то еще лучше будет просто наговорить себе базу wav-ок, загнать в ресурсы или в DLL-ку (это уже для виндузятников :)) и выбирать потихоньку из заместо синтеза.
Но к сожалению, при переходе по ссылке http://****.dlinkddns.com:10001/ браузер пишет "Could not connect to remote server". Пробовал просканировать порт 10001, пишет что порт открыт.
2. В настройках маршрутизатора сделайте проброс портов (Port Forwarding). С 10001 порта на 80. Об этом много статей в интернете, или создайте тему в соответствующем подфоруме по сетям.
Шина не работает со звездой - только шина, а мне не хватает длины. Там же в спеках и рекомендациях на 1820 есть пример с отдельным мультиплексором, чтобы перебирать несколько шлейфов. Но это уже сильно сложнее, чем просто перебирать несколько ног с отдельными шлейфами на дуине (хотелось бы обойтись только ей, нано или микро хватает выше крыши) - благо ног свободных достаточно, но вот как закодить... Не силен. Вроде в скетче видно, что однажды объявляем какая у нас это нога, инициализируем либу - фас на эту ногу, и все, основной цикл только ее и опрашивает. Можно ли подменять гпио, переинициализировать библиотеку в основном цикле? Или инициализировать несколько экземпляров и подправить код на их независимый вызов? Как?