Иногда требуется, не считывая детальную информацию с множества датчиков, просто оценить текущее состояние системы и динамику изменения ее состояния за какой-то период. Вот и мне захотелось сделать устройство, показывающее изменения данных с датчиков в виде красивых картинок небольших диаграмм, которые можно просмотреть в окне браузера мобильных устройств или компьютера, подключенных в локальную сеть. При этом определяющим фактором была минимальная стоимость, и простота реализации.

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

image

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

Аппаратная часть преобразователя состоит из двух частей: платы Arduino Uno, включающей процессор Atmega328, и Ethernet шилда. Были опробованы оба типа имеющихся в семействе Arduino Ethernet-шилдов на ИС ENC28j60 и на W5100.

Возможности вебсервера крайне ограничены размером доступной памяти платы Arduino и архитектурой сетевой платы, но удалось написать скетч, работающий с обеими Ethernet-шилдами.

Программное обеспечение (скетч) для arduino разработано для двух разных условий применения. В первом случае предполагалось, что клиенты, запрашивающие данные, имеют доступ в интернет, что позволяет использовать внешние библиотеки для визуализации. При использовании библиотеки для построения гистограмм от Google внешний вид странички, получаемой от Arduino, может быть таким:

image

Скетч для вывода данных в виде такой диаграммы c Ethernet шилдом на базе чипа W5100 представлен ниже. При желании использовать плату с чипом ENC28J60 необходимо просто раскомментировать 1 строку и закомментировать следующие 2 строки скетча. В программе применены методы оптимизации использования памяти процессора для того, чтобы максимально освободить оперативную память и добиться надежной работы программы на обоих типах сетевых адаптеров.

скетч гистограммы
//вывод данных с помощью web-сервера в виде гистограммы
// автор А. Коновалов 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 в виде линейных графиков:

image

Скетч для такого представления данных представлен ниже:

скетч с линейным графиком
//вывод данных с помощью web-сервера в виде графиков
// автор А. Коновалов 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();
}
}


Кроме того, из чистого любопытства, в рамках исследования возможностей применения библиотеки google был написан скетч, позволяющий выводить информацию в виде аналогового измерительного прибора. Он позволяет быстро оценить текущее значение сигнала.

image

скетч с прибором
//вывод данных с помощью web-сервера в виде прибора
// автор А. Коновалов 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).

Т.к. требуется просто оценить динамику изменения параметров, то в результате выполнения нижеприведенного скетча при запросе данных с ардуино, на экране браузера увидим следующую диаграммку:

image

Один взгляд на диаграммку позволяет понять динамику изменения параметра за определенные ранее интервалы времени.

Скетч приведен ниже:

скетч SVG график
//вывод данных с помощью web-сервера в виде гистограммы
// автор А. Коновалов 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 кода из скетчей. Поэтому, чтобы не разочаровывать читателей, откушенные фрагменты я добавил в виде картинок.
Самое вкусное
SVG

прибор

линии

гистограммы

Комментарии (13)


  1. Color
    05.06.2015 12:13
    +3

    ааа, рекуррентный спойлер!


  1. inhelp
    05.06.2015 13:15

    Хорошая статья, мне понравилась… СПАСИБО!!!


  1. makaroff
    05.06.2015 14:48

    Спасибо за статью. Примерно год-полтора назад мучился с ENC28j60 — что то он у меня так нормально и не взлетел. Веб сервер на ENC28j60 жрёт очень много ресурсов ардуины. Пришлось закупиться W5100.
    PS неделю назад пришел модуль wifi ESP8266 — хотел воткнуть в погодную станцию, но пока не разобрался как организовать с его помощью веб-сервер.


    1. all_kon Автор
      05.06.2015 16:32

      Да, ENC28j60 крови попил. Перепробовал на нем несколько Ethernet библиотек. UIPEthernet.h подкупила тем, что с ней единственной не нужно переписывать код при переходе с W5100.


    1. 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.
      Если это кому-то интересно, я могу написать подробную статью о том, как это сделать.


      1. AlNinyo
        06.06.2015 00:20

        Мне! Мне очень интересно! Дело в том, что у меня уже есть погодная станция такая (DHT11, DS18B20, BMP180, ESP8266), которая заливает данные с датчиков на удалённый сервак в базу MySQL. Но, как-то оно не очень работает. Точнее, как-то криво работают все 4 имеющихся у меня модуля ESP8266.

        А с ENC28j60 у меня ничего не получилось. Примеры кое-как работали, но отправку данных на сервер организовать не удалось. W5100 не имеется в доступе.

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


      1. makaroff
        06.06.2015 12:18

        Дело всё в том, что основой в weather station у меня идёт arduino pro mini, тк используется LCD экран 16х2 на который выводятся дынные о влажности, давлении и температуры в комнате и за окном.


        1. Hellsy22
          06.06.2015 18:21

          Тогда почему бы не связать его с ESP по SoftwareSerial? На 9600 он стабилен. А просто Serial может работать и на более высокой скорости.


          1. makaroff
            06.06.2015 19:31

            Хочется из любой точки мира зайти на свой ip (он у меня естественно статический) и получить web-страничку с температурами.


            1. Hellsy22
              07.06.2015 13:08

              Это понятно, а на каком именно этапе проблема? ESP может обрабатывать http-запросы как сам, так и просто пинать данные на какой-то домашний сервер, который уже будет строить вышеприведенные красивые графики.


              1. AlNinyo
                10.06.2015 21:40

                О, а можно конкретный пример (с кодом, подключением, все дела) того, как к ESP прикрутить BMP180, DS18B20 и научить его отправлять GET/POST запрос с данными на свой собственный сервак своему собственному php-скрипту, который будет получать эти данные и уже закидывать их в базу?

                А то у меня есть 4 ESP, но то ли я сильно криворукий, то ли они багнутые, но мне их ни заставить стандартные примеры воспроизвести не удаётся, ни перепрошить :(


    1. MooM_IYD
      05.06.2015 23:33
      +1

      makaroff,
      Не знаю актуально ли для Вас, но я раскрывал тему с данной платой в статье «Как я искал идею для первого проекта на Arduino или Wake-on-LAN на Arduino»
      Там есть и сырцы.


  1. asci
    06.06.2015 19:42

    www.blynk.cc для смартфонов