После перебора различных вариантов решения этой задачи обратил внимание на микроконтроллеры Arduino. Плюсом данных устройств является простота получения необходимого «железного» функционала путем простого соединения элементов. Например, для получения возможности соединения с локальной сетью достаточно на основную плату надеть сверху плату сетевого адаптера. Главное, чтобы при этом совпали соответствующие разъемы.
Сигнал с датчика приходит на аналоговый вход микроконтроллера, обрабатывается, например, усредняется за необходимые, ранее установленные интервалы времени. Микроконтроллер содержит программное обеспечение, реализующее http-сервер, по запросу с браузера клиента выдающий в локальную сеть html страницу с информацией от датчика. Таким образом, любое устройство, имеющее веб-браузер, например, смартфон и подключенное к локальной сети, имеет возможность просмотра графика.
Аппаратная часть преобразователя состоит из двух частей: платы Arduino Uno, включающей процессор Atmega328, и Ethernet шилда. Были опробованы оба типа имеющихся в семействе Arduino Ethernet-шилдов на ИС ENC28j60 и на W5100.
Возможности вебсервера крайне ограничены размером доступной памяти платы Arduino и архитектурой сетевой платы, но удалось написать скетч, работающий с обеими Ethernet-шилдами.
Программное обеспечение (скетч) для arduino разработано для двух разных условий применения. В первом случае предполагалось, что клиенты, запрашивающие данные, имеют доступ в интернет, что позволяет использовать внешние библиотеки для визуализации. При использовании библиотеки для построения гистограмм от Google внешний вид странички, получаемой от Arduino, может быть таким:
Скетч для вывода данных в виде такой диаграммы c Ethernet шилдом на базе чипа W5100 представлен ниже. При желании использовать плату с чипом ENC28J60 необходимо просто раскомментировать 1 строку и закомментировать следующие 2 строки скетча. В программе применены методы оптимизации использования памяти процессора для того, чтобы максимально освободить оперативную память и добиться надежной работы программы на обоих типах сетевых адаптеров.
// автор А. Коновалов 2015 г.
//#include <UIPEthernet.h> //для работы с ENC28J60
#include <Ethernet.h>
#include <SPI.h
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,26,15);
EthernetServer server(80);
const char str1[] PROGMEM = "<!DOCTYPE html>";
const char str2[] PROGMEM = " »;
const char str12[] PROGMEM = " ";
const char* const string_table[] PROGMEM = {str1, str2, str3, str4, str5, str6, str7};
const char* const string_table2[] PROGMEM = {str8, str9, str10, str11, str12};
char myChar;
char buffer[80];
unsigned long previousMillis1 = 0;// посл момент времени
unsigned long previousMillis2 = 1;// посл момент времени
unsigned long previousMillis3 = 1;// посл момент времени
long OnTime2 = 60000; // минута
long OnTime3 = 1800000; // полчаса
int In_sec = 0; // отсчет за сек
int In_min = 0; // отсчет за мин
int In_half = 0; // отсчет за полчаса
long Sum_min = 0; // сумма за мин
long Sum_half = 0; // сумма за полчаса
float Sum_base_min = 0;
float Sum_base_half = 0;
int i,j,k =0;
void setup()
{
// start the Ethernet connection and the server:
Ethernet.begin(mac, ip);
server.begin();
}
//unsigned long begMillis = millis();// нач время в мс
void loop()
{
unsigned long currentMillis = millis();// тек время в мс
In_sec = analogRead(0);
Sum_min = Sum_min + (currentMillis — previousMillis1) * In_sec;
In_min = (Sum_min + Sum_base_min ) / (OnTime2 * i + currentMillis — previousMillis2);
Sum_half = Sum_half + (currentMillis — previousMillis1) * In_sec;
In_half = (Sum_half + Sum_base_half) / (OnTime3 * j + currentMillis — previousMillis3);
previousMillis1 = currentMillis; // запоминаем момент времени
if(currentMillis — previousMillis2 >= OnTime2)
{ i=1;
Sum_base_min = Sum_min;
previousMillis2 = currentMillis; // запоминаем момент времени
Sum_min = 0;
}
if(currentMillis — previousMillis3 >= OnTime3)
{ j=1;
Sum_base_half = Sum_half;
previousMillis3 = currentMillis; // запоминаем момент времени
Sum_half = 0;
}
EthernetClient client = server.available();
if (client) {
boolean currentLineIsBlank = true;
while (client.connected()) {
if (client.available()) {
char c = client.read();
if (c == '\n' && currentLineIsBlank) {
for (int i = 0; i < 7; i++)
{
strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i])));
client.print(buffer);
delay( 500 );
}
client.print("['Ввод 1', ");
client.print(In_sec);
client.print(", ");
client.print(In_min);
client.print(", ");
client.print(In_half);
client.print( "],]);");
for (int i = 0; i < 5; i++)
{
strcpy_P(buffer, (char*)pgm_read_word(&(string_table2[i])));
client.print(buffer);
delay( 500 );
}
break;
}
if (c == '\n') {
}
else if (c != '\r') {
}
}
}
delay(1);
client.stop();
}
}
В данном скетче считываются данные с нулевого аналогового входа и производится их усреднение за получасовой и минутный интервал. При запросе с http клиента по адресу 192.168.26.15 эти данные выводятся в виде гистограммы. Разумеется, перед загрузкой данной программы необходимо ввести нужный адрес вашей локальной сети.
При желании можно также выводить данные из arduino в виде линейных графиков:
Скетч для такого представления данных представлен ниже:
// автор А. Коновалов 2015 г.
//#include <UIPEthernet.h>
#include <Ethernet.h>
#include <SPI.h>
// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,26,15);
// Initialize the Ethernet server library
// with the IP address and port you want to use
// (port 80 is default for HTTP):
EthernetServer server(80);
const char str1[] PROGMEM = "<!DOCTYPE html><script src=";
const char str2[] PROGMEM = "\«www.google.com/jsapi?autoload={'modules':[{'name':»;
const char str3[] PROGMEM = "'visualization','version':'1','packages':['corechart']}]}\">";
const char str4[] PROGMEM = " »;
const char str12[] PROGMEM = " ";
const char* const string_table[] PROGMEM = {str1, str2, str3, str4, str5, str6, str7};
const char* const string_table2[] PROGMEM = {str8, str9, str10, str11, str12};
char myChar;
char buffer[80];
unsigned long previousMillis1 = 0;// посл момент времени
unsigned long previousMillis2 = 1;// посл момент времени
unsigned long previousMillis3 = 1;// посл момент времени
long OnTime2 = 60000; // минута
long OnTime3 = 600000; // полчаса
int In_sec = 0; // отсчет за сек
int In_min = 0; // отсчет за мин
int In_half = 0; // отсчет за полчаса
long Sum_min = 0; // сумма за мин
long Sum_half = 0; // сумма за полчаса
float Sum_base_min = 0;
float Sum_base_half = 0;
int i,j,k =0;
void setup()
{
// start the Ethernet connection and the server:
Ethernet.begin(mac, ip);
server.begin();
}
//unsigned long begMillis = millis();// тек время в мс
void loop()
{
unsigned long currentMillis = millis();// тек время в мс
In_sec = analogRead(0);
Sum_min = Sum_min + (currentMillis — previousMillis1) * In_sec;
In_min = (Sum_min + Sum_base_min ) / (OnTime2 * i + currentMillis — previousMillis2);
Sum_half = Sum_half + (currentMillis — previousMillis1) * In_sec;
In_half = (Sum_half + Sum_base_half) / (OnTime3 * j + currentMillis — previousMillis3);
previousMillis1 = currentMillis; // запоминаем момент времени
if(currentMillis — previousMillis2 >= OnTime2)
{ i=1;
Sum_base_min = Sum_min;
previousMillis2 = currentMillis; // запоминаем момент времени
Sum_min = 0;
}
if(currentMillis — previousMillis3 >= OnTime3)
{ j=1;
Sum_base_half = Sum_half;
previousMillis3 = currentMillis; // запоминаем момент времени
Sum_half = 0;
}
/*
In_sec = 990;
In_min = 500;
In_half = 90;
*/
// listen for incoming clients
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) {
for (int i = 0; i < 7; i++)
{
strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i]))); // Necessary casts and dereferencing, just copy.
client.print(buffer);
delay( 500 );
}
client.print("['Сейчас', ");
client.print(660);
client.print(", ");
client.print(1120);
client.print( "],]);");
for (int i = 0; i < 5; i++)
{
strcpy_P(buffer, (char*)pgm_read_word(&(string_table2[i]))); // Necessary casts and dereferencing, just copy.
client.print(buffer);
delay( 500 );
}
break;
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
}
else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
// give the web browser time to receive the data
delay(1);
// close the connection:
client.stop();
}
}
Кроме того,
// автор А. Коновалов 2015 г.
//#include <UIPEthernet.h>
#include <Ethernet.h>
#include <SPI.h>
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,26,15);
EthernetServer server(80);
const char str1[] PROGMEM = "<!DOCTYPE html>";
const char str2[] PROGMEM = " »;
const char str12[] PROGMEM = " ";
const char* const string_table[] PROGMEM = {str1, str2, str3, str4, str5, str6, str7};
const char* const string_table2[] PROGMEM = {str8, str9, str10, str11, str12};
char myChar;
char buffer[80];
int In_sec = 0; // отсчет за сек
int i,j,k =0;
void setup()
{
// start the Ethernet connection and the server:
Ethernet.begin(mac, ip);
server.begin();
}
//unsigned long begMillis = millis();// тек время в мс
void loop()
{
In_sec = analogRead(0);
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) {
for (int i = 0; i < 7; i++)
{
strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i]))); // Necessary casts and dereferencing, just copy.
client.print(buffer);
delay( 500 );
}
client.print(In_sec);
client.print("]]);");
for (int i = 0; i < 5; i++)
{
strcpy_P(buffer, (char*)pgm_read_word(&(string_table2[i]))); // Necessary casts and dereferencing, just copy.
client.print(buffer);
delay( 500 );
}
break;
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
}
else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
// give the web browser time to receive the data
delay(1);
// close the connection:
client.stop();
}
}
В случае, если для чтения данных с датчиков нет возможности использовать устройства, связанные с интернетом, опробован другой способ построения диаграмм в Arduino, основанный на использовании Scalable Vector Graphic (SVG).
Т.к. требуется просто оценить динамику изменения параметров, то в результате выполнения нижеприведенного скетча при запросе данных с ардуино, на экране браузера увидим следующую диаграммку:
Один взгляд на диаграммку позволяет понять динамику изменения параметра за определенные ранее интервалы времени.
Скетч приведен ниже:
// автор А. Коновалов 2015 г.
//#include <UIPEthernet.h>
#include <Ethernet.h>
#include <SPI.h>
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,26,15);
EthernetServer server(80);
const char str1[] PROGMEM = "";
const char str2[] PROGMEM = "/>";
const char str4[] PROGMEM = "";
char myChar;
int out;
unsigned long previousMillis1 = 0;// посл момент времени
unsigned long previousMillis2 = 1;// посл момент времени
unsigned long previousMillis3 = 1;// посл момент времени
long OnTime2 = 60000; // минута
long OnTime3 = 600000; // полчаса
int In_sec = 0; // отсчет за сек
int In_min = 0; // отсчет за мин
int In_half = 0; // отсчет за полчаса
long Sum_min = 0; // сумма за мин
long Sum_half = 0; // сумма за полчаса
float Sum_base_min = 0;
float Sum_base_half = 0;
int i,j,k =0;
void setup()
{
Ethernet.begin(mac, ip);
server.begin();
}
//unsigned long begMillis = millis();// тек время в мс
void loop()
{
unsigned long currentMillis = millis();// тек время в мс
In_sec = analogRead(0);
Sum_min = Sum_min + (currentMillis — previousMillis1) * In_sec;
In_min = (Sum_min + Sum_base_min ) / (OnTime2 * i + currentMillis — previousMillis2);
Sum_half = Sum_half + (currentMillis — previousMillis1) * In_sec;
In_half = (Sum_half + Sum_base_half) / (OnTime3 * j + currentMillis — previousMillis3);
previousMillis1 = currentMillis; // запоминаем момент времени
if(currentMillis — previousMillis2 >= OnTime2)
{ i=1;
Sum_base_min = Sum_min;
previousMillis2 = currentMillis; // запоминаем момент времени
Sum_min = 0;
}
if(currentMillis — previousMillis3 >= OnTime3)
{ j=1;
Sum_base_half = Sum_half;
previousMillis3 = currentMillis; // запоминаем момент времени
Sum_half = 0;
}
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) {
for (k = 0; k < strlen(str1); k++)
{
myChar = pgm_read_byte_near(str1 + k);
client.print(myChar);
}
for (k = 0; k < strlen(str2); k++)
{
myChar = pgm_read_byte_near(str2 + k);
client.print(myChar);
}
out = 200 — In_sec/3;
client.print(out);
client.print(" 100,");
client.print(out);
client.print(" 250,");
out = 200 — In_min/3;
client.print(out);
client.print(" 300,");
out = 200 — In_half/3;
client.print(out);
for (k = 0; k < strlen(str3); k++)
{
myChar = pgm_read_byte_near(str3 + k);
client.print(myChar);
}
client.println();
client.print(In_sec);
client.println();
client.print(In_min);
client.println();
client.print(In_half);
for (k = 0; k < strlen(str4); k++)
{
myChar = pgm_read_byte_near(str4 + k);
client.print(myChar);
}
break;
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
}
else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
// give the web browser time to receive the data
delay(1);
// close the connection:
client.stop();
}
}
Приведенные выше скетчи показывают, как самыми простыми средствами, без использования дополнительного оборудования и программных средств, например, веб-сервера и серверных приложений обработки данных, получить приемлемый в условиях эксплуатации вид отображения информации с датчиков. Конечно, при данном способе передачи данных необходимо наличие локальной сети и для работы мобильных устройств, Wi-Fi доступа в эту сеть.
При написании скетчей большую помощь оказало руководство для разработчика, а также статья на Хабре «Знакомство с SVG-графикой».
Библиотека UIP не входит в стандартный набор, но может быть загружена здесь.
Надеюсь, опираясь на приведенные мной примеры скетчей и ссылки на обучающие материалы вам будет легко заставить Arduino выводить графики в том виде, который вам больше понравится.
P.S. Как оказалось, спойлер хабра безжалостно выгрыз куски html кода из скетчей. Поэтому, чтобы не разочаровывать читателей, откушенные фрагменты я добавил в виде картинок.
Комментарии (13)
makaroff
05.06.2015 14:48Спасибо за статью. Примерно год-полтора назад мучился с ENC28j60 — что то он у меня так нормально и не взлетел. Веб сервер на ENC28j60 жрёт очень много ресурсов ардуины. Пришлось закупиться W5100.
PS неделю назад пришел модуль wifi ESP8266 — хотел воткнуть в погодную станцию, но пока не разобрался как организовать с его помощью веб-сервер.all_kon Автор
05.06.2015 16:32Да, ENC28j60 крови попил. Перепробовал на нем несколько Ethernet библиотек. UIPEthernet.h подкупила тем, что с ней единственной не нужно переписывать код при переходе с W5100.
Hellsy22
05.06.2015 18:36+1но пока не разобрался как организовать с его помощью веб-сервер.
Вот простейший пример кода для прошивки nodeMCU:
s=net.createServer(net.TCP) s:listen(80,function(c) c:on("receive",function(c,pl) c:send("HTTP/1.1 200 OK\n\n"); c:send("Hello world!") c:on("sent",function(c) c:close() end) end) end)
В сети есть множество примеров создания погодных станций на DHT22 и ESP8266.
Если это кому-то интересно, я могу написать подробную статью о том, как это сделать.AlNinyo
06.06.2015 00:20Мне! Мне очень интересно! Дело в том, что у меня уже есть погодная станция такая (DHT11, DS18B20, BMP180, ESP8266), которая заливает данные с датчиков на удалённый сервак в базу MySQL. Но, как-то оно не очень работает. Точнее, как-то криво работают все 4 имеющихся у меня модуля ESP8266.
А с ENC28j60 у меня ничего не получилось. Примеры кое-как работали, но отправку данных на сервер организовать не удалось. W5100 не имеется в доступе.
И да, огромное спасибо за статью. Как раз ломал свою пустую голову на тему «как выводить на сайте графики температуры и прочего»!
makaroff
06.06.2015 12:18Дело всё в том, что основой в weather station у меня идёт arduino pro mini, тк используется LCD экран 16х2 на который выводятся дынные о влажности, давлении и температуры в комнате и за окном.
Hellsy22
06.06.2015 18:21Тогда почему бы не связать его с ESP по SoftwareSerial? На 9600 он стабилен. А просто Serial может работать и на более высокой скорости.
makaroff
06.06.2015 19:31Хочется из любой точки мира зайти на свой ip (он у меня естественно статический) и получить web-страничку с температурами.
Hellsy22
07.06.2015 13:08Это понятно, а на каком именно этапе проблема? ESP может обрабатывать http-запросы как сам, так и просто пинать данные на какой-то домашний сервер, который уже будет строить вышеприведенные красивые графики.
AlNinyo
10.06.2015 21:40О, а можно конкретный пример (с кодом, подключением, все дела) того, как к ESP прикрутить BMP180, DS18B20 и научить его отправлять GET/POST запрос с данными на свой собственный сервак своему собственному php-скрипту, который будет получать эти данные и уже закидывать их в базу?
А то у меня есть 4 ESP, но то ли я сильно криворукий, то ли они багнутые, но мне их ни заставить стандартные примеры воспроизвести не удаётся, ни перепрошить :(
MooM_IYD
05.06.2015 23:33+1makaroff,
Не знаю актуально ли для Вас, но я раскрывал тему с данной платой в статье «Как я искал идею для первого проекта на Arduino или Wake-on-LAN на Arduino»
Там есть и сырцы.
Color
ааа, рекуррентный спойлер!