С момента моего последнего поста про умный дом прошло много времени. Я решил его делать начиная с погодной станции.
Рисунок 1 — Фотография макетного образца
Несмотря на обилие статей про погодные станции на arduino (http://habrahabr.ru/post/165747/, habrahabr.ru/post/171525/, habrahabr.ru/post/213405/ ) Я все-таки решил опубликовать своё решение.
Функционал
Функции которые она выполняет:
- Измерение температуры
- Измерение влажности
- Измерение давления
- Измерение освещенности
- Индикация измеренных параметров
- Выдача измеренных параметров по интерфейсу HTTP в виде XML документа
- Выдача по протоколу HTTP XSLT процессора для стилизации XML при отображении браузером
- Выдача информации по Modbus (его предполагаю использовать в качестве протокола управления умным домом)
- Питание через Passive POE
Функции которые не удалось реализовать, но хотелось:
- Измерение наличие осадков
- Направление ветра
- Скорость ветра
- DHCP — клиент
Стоимость ~1700руб (по курсу на момент покупки), в него входило:
- DHT22 Digital Temperature And Humidity Sensor $ 5.13
- GY-65 BMP085 Atmospheric Pressure Altimeter Module $ 4.09
- GY-302 BH1750 Chip Light Intensity Light Module $ 2.85
- IBOARD W5100 Ethernet Module for Arduino Development Board with POE / Xbee and SD Card Slot Expansion $ 18.46
- MAX7219 Digital Tube Display Module$ 4.74
- 40pcs 20cm 2.54mm 1p-1p pin Dupont wire cable Line connector $ 2.42
- FT232RL USB To Serial Line Download Line Downloader USB TO 232 $ 4.47
- Корпус пластиковый ~100 руб
Реализация устройства
Устройство было полностью собрано из покупных компонентов на базе Arduino. Все датчики были соединены проводами и расположены внутри покупного корпуса эля электроустройств. Прозрачные окна в опытном образце сделаны из прозрачной липкой ленты.
В устройстве предусмотрен LED-дисплей, который будет хорошо работать при отрицательных температурах, и позволяет использовать устройство по назначению без компьютера. Для удобства использования в ночное время — в ПО организована обратная связь по освещенности.
В качестве мозгов выбрана плата Iboard w5100, по причине того, что в ней уже присутствует весь функцонал arduino + Ethernet shield + sensor board для размножения питания и земли, при подключении большого количества устройств. Такая высокоинтегрированная плата съэкономит деньги и место. Также эта плата поддерживает работу с Passiv POE.
Передачу данных я решил сделать проводным (в отличие от большинства решений умного дома) по следующим причинам:
- Провод в любом случае тянуть — будь то питания или передачи данных
- В случае если решено запитать устройство от химических источников питания — то будет возникать вопрос их замены
- Проводные коммуникации кажутся более надёжными:
- К ним не будет претензий со стороны Роскомнадзора, так как ничего не излучают в эфир
- Не возникнет проблем со взаимным влиянием других приемо-передатчиков в той-же частоты
- Не возникнет огромного количества проблем связанных с реализацией радиосвязи (временного разделения эфира, затухания сигнала, влиение других таких-же систем при перекрывании зон приёма, и.т.д.)
- Будут спокойны люди, которым Wifi греет мозг.
Чтобы не плодить интерфейсы, и все сделать единообразно — я решил для передачи данных использовать Ethernet. Питание передавать по этому-же проводу, используя технологию Passiv POE. Достоинства этого способа — если все устройства будут подключены в общую Ethernet сеть — то не возникнет вопроса с протокольными конвекторами/шлюзами.
Рисунок 2 — Блок схема устройства
Рисунок 3 — схема соединения
Рисунок 4 — Вид изнутри
Реализация Passiv POE
Passive POE — Это особая реализация модули POE. В оригинальном POE требуется реализация протокола, при этом инжектор при установки связи определяет мощность удаленного оборудования, и если он может его запитать — то записывает. Passive POE придумали хитрые китайцы, которые не хотели делать такую хитрую реализацию, но хотели выйти на рынок POE оборудования, а результате они придумали тупо подать напряжение на неиспользуемые витые пары кабеля категории 5E. Некоторые даже пишут что они якобы придерживаются стандарта IEEE 802.3af в части электрических характеристик, однако даже это не всегда так.
IBOARD W5100 работает, если на выводы 4,5 и 7,8 разъема Ethernet подать напряжение 6-20В. Я подаю 20 В. По идее
Рисунок 5 — Схема соединения для инжектирования POE
Инжектор я вмонтировал в D-LINK 320, установив в него китайский повышающий DC-DC преобразователь.
Рисунок 6 — Фотография модификации
После монтажа — все заработало.
Внимание! Перед включением проверьте, что источник питания настроен на выдачу 20В.
Рисунок 7 — Работы всей конструкции
Реализация WEB-интерфейса
Web-интерфейс был сделан, для наиболее удобного взаимодействия с устройством из сети без дополнительного ПО на стороне клиента. Для возможности автоматизированного доступа к данным было бы удобно выдавать информацию в виде XML файла. В данном проекта было совмещено эти два способа доступа к данным посредством использования XSLT процессора.
Web-браузер посылает запрос на 192.168.0.20/; Arduino в ответ отправляет XML документ:
<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="http://192.168.0.20/z1.xsl"?> <response> <temperature> <celsius>30.70</celsius> <sensors> <sensor name='BMP' unit='C'>32.62</sensor> <sensor name='DHT' unit='C'>30.70</sensor> </sensors> </temperature> <humidity> <percentage>21.60</percentage> </humidity> <pressure> <pa>99309</pa> <mmHg>745</mmHg> </pressure> <illuminance> <lx>11</lx> </illuminance> </response>
Для отображения этой информации в красивом виде браузер загружает XSLT процессор, и с помощью него генерирует HTML документ.
<?xml version='1.0' encoding='UTF-8'?> <xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'> <xsl:template match='/'> <html> <head> <title>Weather station</title> <meta http-equiv='refresh' content='5'/> <style> .z1 { font-family:Arial, Helvetica, sans-serif; color:#666; font-size:12px; text-shadow: 1px 1px 0px #fff; background:#eaebec; margin:20px; border:#ccc 1px solid; border-collapse:separate; border-radius:3px; box-shadow: 0 1px 2px #d1d1d1; } .z1 th { font-weight:bold; padding:15px; border-bottom:1px solid #e0e0e0; background: #ededed; background: linear-gradient(to top, #ededed, #ebebeb); } .z1 td { padding:10px; background: #f2f2f2; background: linear-gradient(to top, #f2f2f2, #f0f0f0); } .z1 tr:hover td{ background: #aaaaaa; background: linear-gradient(to top, #f2f2f2, #e0e0e0); } </style> </head> <body> <h2>Weather station</h2> <table class='z1'> <tr> <th>Property</th> <th>Value</th> </tr> <tr> <td> Temperature </td> <td><xsl:value-of select='response/temperature/celsius'/> C</td> </tr> <tr> <td> Humidity </td> <td><xsl:value-of select='response/humidity/percentage'/> %</td> </tr> <tr> <td> Pressure </td> <td><xsl:value-of select='response/pressure/mmHg'/> mm.Hg</td> </tr> <tr> <td> Illuminance </td> <td><xsl:value-of select='response/illuminance/lx'/> lx</td> </tr> </table> <h2>Termosensor</h2> <table class='z1'> <tr> <th>Sensor</th> <th>Value</th> </tr> <xsl:for-each select='response/temperature/sensors/sensor'> <tr> <td> <xsl:value-of select='@name'/> </td> <td><xsl:value-of select='.'/> <xsl:value-of select='@unit'/></td> </tr> </xsl:for-each> </table> </body> </html> </xsl:template> </xsl:stylesheet>
В результате всех этих мероприятий этот XML документ в Web-браузере выглядит вот так:
Рисунок 8 — Внешний вид web-интерфейса в Firefox
Программное обечпечение
В ПО я старался по максимуму использовать готовые библиотеки, чтобы ускорить процесс.
Логика программы достаточно простая — инициализируем все устройства, вызывая соответствующие функции из библиотек.
В цикле считываем все показания датчиков, если необходимо — обновляем данные на 8-разрядном LED-индикаторе и обрабатываем запрос по сети Ethernet.
В проекте были использованы следующие библиотеки:
- DHTLib — Библиотека доступа к датчику температуры/влажности
- Adafruit-BMP085-Library — Библиотека доступа к датчику давления/температуры
- BH1750 — Библиотека доступа к датчику освещенности
- LedControl — Библиотека работы с LED индикатором по птоколу SPI
- Webduino — Библиотека реализации протокола HTTP
- Mudbus — Библиотека реализации протокола Modbus
Стоит заметить, что для вывода букв — мне пришлось модернизировать таблицу символов в библиотеке LedControl. По умолчанию эта библиотека может отображать только буквы a-f.
#include <Ethernet.h> #include <SPI.h> #include <Wire.h> #include "DHT.h" #include "BH1750.h" #include "Adafruit_BMP085.h" #include "LedControl.h" #include "Mudbus.h" #include "WebServer.h" #define WEATHER_STATION_Z1 0x20 // =============================================================== #define DHT_S1_PIN A0 // пин для датчика DHT22 // =============================================================== // assign a MAC address for the ethernet controller. // fill in your address here: byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; // assign an IP address for the controller: IPAddress ip(192,168,0,20); IPAddress gateway(192,168,0,1); IPAddress subnet(255, 255, 255, 0); // =============================================================== float humidity = 0, temp_dht = 0, temp_bmp = 0, temp = 0; uint16_t light = 0; int32_t pressure_pa = 0, pressure_mm = 0; int mode = 0; dht dht_s1; BH1750 lightMeter; Adafruit_BMP085 bmp; /* This creates an instance of the webserver. By specifying a prefix * of "", all pages will be at the root of the server. */ #define PREFIX "" WebServer webserver(PREFIX, 80); //EthernetServer webserver(80); #define DEV_ID Mb.R[0] #define TEMPERATURE Mb.R[1] #define TEMPERATURE_DHT Mb.R[2] #define TEMPERATURE_BMP Mb.R[3] #define HUMIDITY Mb.R[4] #define PRESSURE_MM Mb.R[5] #define LIGHT Mb.R[6] Mudbus Mb; // pin A5 is connected to the DataIn // pin A6 is connected to the CLK // pin A7 is connected to LOAD LedControl lc=LedControl(A1,A2,A3,1); // ======================== Web pages ========================== void web_index(WebServer &server, WebServer::ConnectionType type, char *, bool) { /* this line sends the standard "we're all OK" headers back to the browser */ server.httpSuccess("application/xml; charset=utf-8"); /* if we're handling a GET or POST, we can output our data here. For a HEAD request, we just stop after outputting headers. */ if (type != WebServer::HEAD) { /* this defines some HTML text in read-only memory aka PROGMEM. * This is needed to avoid having the string copied to our limited * amount of RAM. */ P(index_p1) = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" "<?xml-stylesheet type=\"text/xsl\" href=\"http://192.168.0.20/z1.xsl\"?>" "<response>" " <temperature>" " <celsius>"; P(index_p2) = "</celsius>" " <sensors>" " <sensor name='BMP' unit='C'>"; P(index_p3) = "</sensor>" " <sensor name='DHT' unit='C'>"; P(index_p4) = "</sensor>" " </sensors>" " </temperature>" " <humidity>" " <percentage>"; P(index_p5) = "</percentage>" " </humidity>" " <pressure>" " <pa>"; P(index_p6) = "</pa>" " <mmHg>"; P(index_p7) = "</mmHg>" " </pressure>" " <illuminance>" " <lx>"; P(index_p8) = "</lx>" " </illuminance>" "</response>"; /* this is a special form of print that outputs from PROGMEM */ server.printP(index_p1); server.print(temp); server.printP(index_p2); server.print(temp_bmp); server.printP(index_p3); server.print(temp_dht); server.printP(index_p4); server.print(humidity); server.printP(index_p5); server.print(pressure_pa); server.printP(index_p6); server.print(pressure_mm); server.printP(index_p7); server.print(light); server.printP(index_p8); } } void web_z1_xsl(WebServer &server, WebServer::ConnectionType type, char *, bool) { server.httpSuccess("text/xsl; charset=utf-8"); if (type != WebServer::HEAD) { P(z1_xsl) = "<?xml version='1.0' encoding='UTF-8'?>" "<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>" "<xsl:template match='/'>" " <html>" " <head>" " <title>Weather station</title>" " <meta http-equiv='refresh' content='5'/>" " <style>" " .z1 {" " font-family:Arial, Helvetica, sans-serif;" " color:#666;" " font-size:12px;" " text-shadow: 1px 1px 0px #fff;" " background:#eaebec;" " margin:20px;" " border:#ccc 1px solid;" " border-collapse:separate; " " border-radius:3px;" " box-shadow: 0 1px 2px #d1d1d1;" " }" " .z1 th {" " font-weight:bold;" " padding:15px;" " border-bottom:1px solid #e0e0e0;" " background: #ededed;" " background: linear-gradient(to top, #ededed, #ebebeb);" " }" " .z1 td {" " padding:10px;" " background: #f2f2f2;" " background: linear-gradient(to top, #f2f2f2, #f0f0f0); " " }" " .z1 tr:hover td{" " background: #aaaaaa;" " background: linear-gradient(to top, #f2f2f2, #e0e0e0); " " }" " </style>" " </head>" " <body>" " <h2>Weather station</h2>" " <table class='z1'>" " <tr>" " <th>Property</th>" " <th>Value</th>" " </tr>" " <tr>" " <td> Temperature </td>" " <td><xsl:value-of select='response/temperature/celsius'/> C</td>" " </tr>" " <tr>" " <td> Humidity </td>" " <td><xsl:value-of select='response/humidity/percentage'/> %</td>" " </tr>" " <tr>" " <td> Pressure </td>" " <td><xsl:value-of select='response/pressure/mmHg'/> mm.Hg</td>" " </tr>" " <tr>" " <td> Illuminance </td>" " <td><xsl:value-of select='response/illuminance/lx'/> lx</td>" " </tr>" " </table>" " <h2>Termosensor</h2>" " <table class='z1'>" " <tr>" " <th>Sensor</th>" " <th>Value</th>" " </tr>" " <xsl:for-each select='response/temperature/sensors/sensor'>" " <tr>" " <td> <xsl:value-of select='@name'/> </td>" " <td><xsl:value-of select='.'/> <xsl:value-of select='@unit'/></td>" " </tr>" " </xsl:for-each>" " </table>" " </body>" " </html>" "</xsl:template>" "</xsl:stylesheet>"; /* this is a special form of print that outputs from PROGMEM */ server.printP(z1_xsl); } } // ========================СТАРТУЕМ============================= void setup(){ // Init LED display lc.shutdown(0,false); lc.setIntensity(0,2); lc.clearDisplay(0); lc.setChar(0,7,'L',false); lc.setChar(0,6,'O',false); lc.setChar(0,5,'A',false); lc.setChar(0,4,'d',false); //запускаем Ethernet SPI.begin(); Ethernet.begin(mac, ip); // Init Light sensor lightMeter.begin(); // Init pressure sensor if (!bmp.begin()) { Serial.println("ERROR: BMP085 sensor failed"); } //enable serial datada print Serial.begin(9600); Serial.println("Weather Z1 v 0.1"); // Тестовые строки для отображения в мониторе порта webserver.setDefaultCommand(&web_index); webserver.addCommand("index.html", &web_index); webserver.addCommand("z1.xsl", &web_z1_xsl); webserver.begin(); } void loop(){ char buff[64]; int len = 64; mode = (mode + 1) % 100; Z1_sensors_update(); Z1_SerialOutput(); Z1_ledDisplay(); Z1_modbus_tcp_slave(); // Z1_http_server(); webserver.processConnection(buff, &len); } void Z1_sensors_update() { if (mode%30==0) { // BH1750 (light) light = lightMeter.readLightLevel(); // BMP085 (Temp and Pressure) temp_bmp = bmp.readTemperature(); pressure_pa = bmp.readPressure(); pressure_mm = pressure_pa/133.3; // DHT22 (Temp) if (dht_s1.read22(DHT_S1_PIN) == DHTLIB_OK) { humidity = dht_s1.humidity; temp_dht = dht_s1.temperature; temp = temp_dht; } else { temp = temp_bmp; } } } void Z1_SerialOutput() { Serial.print("T1= "); Serial.print(temp_dht); Serial.print(" *C \t"); Serial.print("T2= "); Serial.print(temp_bmp); Serial.print(" *C \t"); Serial.print("Pressure= "); Serial.print(pressure_mm); Serial.print(" mm \t"); Serial.print("Humidity= "); Serial.print(humidity); Serial.print(" %\t"); Serial.print("Light= "); Serial.print(light); Serial.print(" lx \t"); Serial.print("\n"); } void Z1_ledDisplay() { int v; if (light<50) { lc.setIntensity(0,0); } else if (light>80 && light<200) { lc.setIntensity(0,2); } else if (light>250 && light<1000) { lc.setIntensity(0,5); } else if (light>1100) { lc.setIntensity(0,15); } if (mode<=25) { // lc.clearDisplay(0); lc.setChar(0,7,'t',false); if (temp>=0) { lc.setChar(0,6,' ',false); } else { lc.setChar(0,6,'-',false); } v = (int)( temp / 10 ) % 10; lc.setDigit(0,5,(byte)v,false); v = (int)( temp ) % 10; lc.setDigit(0,4,(byte)v,true); v = (int)( temp * 10 ) % 10; lc.setDigit(0,3,(byte)v,false); lc.setChar(0,2,' ',false); lc.setChar(0,1,'*',false); lc.setChar(0,0,'C',false); delay(1); } else if (mode<=50) { // lc.clearDisplay(0); lc.setChar(0,7,'H',false); lc.setChar(0,6,' ',false); v = (int)( humidity / 10 ) % 10; lc.setDigit(0,5,(byte)v,false); v = (int)( humidity ) % 10; lc.setDigit(0,4,(byte)v,true); v = (int)( humidity * 10 ) % 10; lc.setDigit(0,3,(byte)v,false); lc.setChar(0,2,' ',false); lc.setChar(0,1,'*',false); lc.setChar(0,0,'o',false); delay(1); } else if (mode<=75) { // lc.clearDisplay(0); lc.setChar(0,7,'P',false); lc.setChar(0,6,' ',false); v = (int)( pressure_mm / 100 ) % 10; lc.setDigit(0,5,(byte)v,false); v = (int)( pressure_mm/10 ) % 10; lc.setDigit(0,4,(byte)v,false); v = (int)( pressure_mm ) % 10; lc.setDigit(0,3,(byte)v,false); lc.setChar(0,2,' ',false); lc.setChar(0,1,'n',false); lc.setChar(0,0,'n',false); delay(1); } else { // lc.clearDisplay(0); lc.setChar(0,7,'L',false); lc.setChar(0,6,' ',false); v = (int)( light / 1000 ) % 10; lc.setDigit(0,5,(byte)v,false); v = (int)( light / 100 ) % 10; lc.setDigit(0,4,(byte)v,false); v = (int)( light / 10 ) % 10; lc.setDigit(0,3,(byte)v,false); v = (int)( light ) % 10; lc.setDigit(0,2,(byte)v,false); lc.setChar(0,1,' ',false); lc.setChar(0,0,' ',false); delay(1); } } void Z1_modbus_tcp_slave() { Mb.Run(); DEV_ID = WEATHER_STATION_Z1; TEMPERATURE = temp*10; TEMPERATURE_DHT = temp_dht*10; TEMPERATURE_BMP = temp_bmp*10; HUMIDITY = humidity*10; PRESSURE_MM = pressure_mm; LIGHT = light; }
Git-репозиторий с ПО контроллера Arduino: github.com/krotos139/sh1_arduino_weather_station_v1
Вывод
Умная погодная станция с интерфейсом Ethernet может быть легко реализована на arduino.
Себестоимость устройства при опытном производстве — ~1700 руб
Время на сборку — ~1 день
Функциональность фантастическая — может работать как самостоятельное устройство с питанием от POE и от стандартного arduino-вского источника питания, так и как smart-устройство — позволяя из браузера получать всю необходимую информацию. Для автоматизированной обработки информации устройство предоставляет информацию в виде XML документа и по протоколу modbus.
ссылка на оригинал статьи http://habrahabr.ru/post/214011/
Добавить комментарий