Доброго времени суток хабр-сообщество.
С момента моего последнего поста про умный дом прошло много времени. Я решил его делать начиная с погодной станции.
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.
Передачу данных я решил сделать проводным (в отличие от большинства решений умного дома) по следующим причинам:
Провод в любом случае тянуть — будь то питания или передачи данных
В случае если решено запитать устройство от химических источников питания — то будет возникать вопрос их замены
Проводные коммуникации кажутся более надёжными:
К ним не будет претензий со стороны Роскомнадзора, так как ничего не излучают в эфир
Не возникнет проблем со взаимным влиянием других приемо-передатчиков в той-же частоты
Не возникнет огромного количества проблем связанных с реализацией радиосвязи (временного разделения эфира, затухания сигнала, влиение других таких-же систем при перекрывании зон приёма, и.т.д.)
Чтобы не плодить интерфейсы, и все сделать единообразно — я решил для передачи данных использовать 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 документ:
Стоит заметить, что для вывода букв — мне пришлось модернизировать таблицу символов в библиотеке 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 ==========================voidweb_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);
}
}
voidweb_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);
}
}
// ========================СТАРТУЕМ=============================voidsetup(){
// 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 sensorif (!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();
}
voidloop(){
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);
}
voidZ1_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;
}
}
}
voidZ1_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");
}
voidZ1_ledDisplay(){
int v;
if (light<50) {
lc.setIntensity(0,0);
} elseif (light>80 && light<200) {
lc.setIntensity(0,2);
} elseif (light>250 && light<1000) {
lc.setIntensity(0,5);
} elseif (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);
} elseif (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);
} elseif (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);
}
}
voidZ1_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;
}
Умная погодная станция с интерфейсом Ethernet может быть легко реализована на arduino.
Себестоимость устройства при опытном производстве — ~1700 руб
Время на сборку — ~1 день
Функциональность фантастическая — может работать как самостоятельное устройство с питанием от POE и от стандартного arduino-вского источника питания, так и как smart-устройство — позволяя из браузера получать всю необходимую информацию. Для автоматизированной обработки информации устройство предоставляет информацию в виде XML документа и по протоколу modbus.