- Web-server на Arduino для представления графиков

Web-server на Arduino для представления графиков температуры, давления, влажности

Практика для студентов. Мясищев А.А.

Более подробное описание можно посмотреть здесь

Фото web-сервера на Arduino mega2560

Интерфейсы главной страницы сервера, график температуры вне помещения и текущие показания датчиков с установкой времени и даты

Для формирования графиков в браузере клиента используется библиотека dygraph. Сформированные на MicroSD карте данные при опросе датчиков вместе с файлами библиотеки передаются на браузер клиента, который формирует и строит графики. Web - server позволяет формировать графики в течении 3-х суток. Для этого необходимо зайти на страничку "текущие показания датчиков, настройки" и установить время и 3-и последовательные даты, для которых требуются графики. После 3-х суток в файлы данных информация с датчиков заноситься не будет. Так как данные с датчиков добавляются в файлы, то после перегрузки сервера можно внести следующие последовательные 3-и даты. Таким образом на графиках будет отображено еще дополнительно трое суток. Для построения новых графиков необходимо карту microSD переставить на компьютер и удалить данные с файлов datat.txt, datata.txt, datap.txt, datah.txt, содержимое которых описано ниже.

Программа Arduino IDE ver.1.0.5-r2

#include <Wire.h>

#include <LiquidCrystal.h>

#include <MS5611.h>

#include <SPI.h>

#include <SD.h>

#include <Ethernet.h>

#include <OneWire.h> //Подключаем описание библиотеки шины OneWire

#include <DallasTemperature.h> //Подключаем описание библиотеки для температуры(DS18B20)

#include "DHT.h"

#define FORM "<FORM action=\"\" >"

#define ONE_WIRE_BUS 6

#define DHTPIN 3 // 2 Pin DHT11 подключен к 3-му выводу Arduino

#define DHTTYPE DHT11 // Выбран датчик DHT 11

const int rs = 13, en = 12, d4 = 11, d5 = 9, d6 = 8, d7 = 5;

LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

OneWire oneWire(ONE_WIRE_BUS); //Настройка шины для работы с 6-м выводом Ардуино

DallasTemperature sensors(&oneWire); //Подключаем датчик температуры

DHT dht(DHTPIN, DHTTYPE);

MS5611 ms5611;

float cel=0;

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xE2 };

IPAddress ip(192,168,1,38);

char rootFileName[] = "index.htm";

EthernetServer server(9000);

File myFile;

File myFile1;

File myFile2;

File myFile3;

File myFile4;

int clo=0;

int sec=0, mi=0, ho=0, time=0, day=0;

int endn=1, ends=1, newf=1, endgraf=0;

char fh[3], fm[3], fd0[11], fd1[11], fd2[11];

void setup() {

pinMode(53, OUTPUT); // for arduino mega

// Serial.begin(38400);

lcd.begin(16, 2);

Ethernet.begin(mac, ip); // По умолчанию шлюз 192.168.1.1, маска 255.255.255.0

server.begin();

SD.begin(4);

dht.begin(); // Запуск библиотеки DHT

sensors.begin(); //Инициализация датчика температуры DS18B20

ms5611.begin(MS5611_HIGH_RES); //Initialize MS5611 Sensor

fd0[0]=0;

CLKPR=0x80;CLKPR=0x00;

TCCR5A=0x00 ;TCCR5B=0x0C;//Делитель тактовой частоты счетчика (256)

TCNT5L=0x00; TCNT5H=0x00; ICR5L=0x00; ICR5H=0x00;

OCR5AH=0xF1; OCR5AL=0xE5; // Число тактов для достижения 1 секунды

OCR5BH=0x00; OCR5BL=0x00; OCR5CH=0x00; OCR5CL=0x00;

TIMSK5=0x02; // Прерывание при совпадении с A

meter();

}

ISR (TIMER5_COMPA_vect)

{

sec++;

time++;

if(sec==18) ends=0;

if(time==299) endn=0;

if(time==300) time=0;

if(sec==60) { mi++; sec=0; }

if(mi==60) { ho++; mi=0; sec=0; }

if(ho==24) { ho=0; mi=0; sec=0; day++; }

if(day==3) { endgraf=1; day=0; }

// Serial.print("sec=");Serial.println(sec);

}

// How big our line buffer should be. 100 is plenty!

#define BUFSIZ 100

void loop()

{

int soc0=0,soc1=0,soc2=0,soc3=0,len0=0,len1=0,len2=0,len3=0;

char clientline[BUFSIZ];

char *filename;

int index = 0;

char celbuf[10];

char cel1buf[10];

char prebuf[10];

char hobuf[10];

char mibuf[10];

char secbuf[10];

char hubuf[10];

if(sec==18 && ends==0)

{

ends=1;

meter(); // Распечатка на LCD

}

if( time==299 && endn==0 && endgraf==0 ) // При совпадении времени пишем данные в файл и так до 3-го дня при достижении 24 часов

{ endn=1; // Чтобы была только 1 запись при time=299

// Serial.println("Write");

myFile1 = SD.open("datat.txt", FILE_WRITE);

if (myFile1) {

sensors.requestTemperatures();

cel=sensors.getTempCByIndex(0);

dtostrf(cel,6,2,celbuf);

dtostrf(ho,2,0,hobuf);

dtostrf(mi,2,0,mibuf);

if(day==0) myFile1.print(fd0);

else if(day==1) myFile1.print(fd1);

else if(day==2) myFile1.print(fd2);

myFile1.print(" ");

myFile1.print(hobuf);

myFile1.print(":");

myFile1.print(mibuf);

myFile1.print(",");

myFile1.println(celbuf);

myFile1.close();

}

myFile2 = SD.open("datata.txt", FILE_WRITE);

if (myFile2) {

double cel1=ms5611.readTemperature();

dtostrf(cel1,6,2,cel1buf);

dtostrf(ho,2,0,hobuf);

dtostrf(mi,2,0,mibuf);

if(day==0) myFile2.print(fd0);

else if(day==1) myFile2.print(fd1);

else if(day==2) myFile2.print(fd2);

myFile2.print(" ");

myFile2.print(hobuf);

myFile2.print(":");

myFile2.print(mibuf);

myFile2.print(",");

myFile2.println(cel1buf);

myFile2.close();

}

myFile3 = SD.open("datap.txt", FILE_WRITE);

if (myFile3) {

double pre=0.007501*ms5611.readPressure();

dtostrf(pre,7,1,prebuf);

dtostrf(ho,2,0,hobuf);

dtostrf(mi,2,0,mibuf);

if(day==0) myFile3.print(fd0);

else if(day==1) myFile3.print(fd1);

else if(day==2) myFile3.print(fd2);

myFile3.print(" ");

myFile3.print(hobuf);

myFile3.print(":");

myFile3.print(mibuf);

myFile3.print(",");

myFile3.println(prebuf);

myFile3.close();

}

myFile4 = SD.open("datah.txt", FILE_WRITE);

if (myFile4) {

float hu = dht.readHumidity();

dtostrf(hu,7,0,hubuf);

dtostrf(ho,2,0,hobuf);

dtostrf(mi,2,0,mibuf);

if(day==0) myFile4.print(fd0);

else if(day==1) myFile4.print(fd1);

else if(day==2) myFile4.print(fd2);

myFile4.print(" ");

myFile4.print(hobuf);

myFile4.print(":");

myFile4.print(mibuf);

myFile4.print(",");

myFile4.println(hubuf);

myFile4.close();

}

}

aa:

int si=0;

EthernetClient client;

client = server.available(); // server.available() - открытие socket и его прослушивание

soc0=client.sockstat(0);soc1=client.sockstat(1);soc2=client.sockstat(2);soc3=client.sockstat(3);

if (soc0==0x17 ||soc1 ==0x17 ||soc2==0x17 ||soc3==0x17 ) // Если соединение есть (ESTABLISHMENT) то определяем

// длину поля данных пакета TCP. Если эта длина для активного socket 25 раз равна 0, то срочно закрываем соединение

// иначе сервер "зависнет". Причем зависание происходит при подключении через Интернет. По всей видимости пакеты

// на закрытие соединения теряются, соединение остается открытым, открытыми остается много соединений и их надо

// принудительно закрывать со стороны сервера.

{

delay(5);

if (soc0==0x17) {si=0; goto ss;}

if (soc1==0x17) {si=1; goto ss;}

if (soc2==0x17) {si=2; goto ss;}

if (soc3==0x17) si=3;

ss:

if ((clo>=25) && (client.available() == 0 ))//Если соединение не закрыто клиентом, интенсивно закрываем его сервером

{

clo=0; delay(10);

client.disc(0); client.disc(1); client.disc(2); client.disc(3);delay(50);client.clos(0);client.clos(1);

client.clos(2);client.clos(3);

goto aa; // Опять открываем socket и начинаем его прослушивание

}

clo++; delay(50);

if (client.available() > 0) clo=0;

if (client) {

index = 0;

while (client.connected()) {

if (client.available()) {

char c = client.read();

// Если идет чтение не новой строки, то продолжаем ее символы записывать в буфер.

if (c != '\n' && c != '\r') {

if (index <= BUFSIZ) clientline[index] = c;

index++;

// Идем на продолжение считывать новый символ.

continue;

}

// Заканчиваем строку символом 0, если следующая строка новая ( получили \n или \r )

clientline[index] = 0;

filename = 0;

// Распечатываем прочитанную строку.

// Serial.println(clientline);

if(strstr(clientline, "GET /temp?c=") !=0) // Распознаем время

{

fh[0]=clientline[12]; fh[1]=clientline[13]; fh[2]=0;

fm[0]=clientline[14]; fm[1]=clientline[15]; fm[2]=0;

ho=atoi(fh); mi=atoi(fm);

}

if(strstr(clientline, "GET /temp?d=") !=0) // Распознаем 1-ю дату

{

fd0[0]=clientline[12]; fd0[1]=clientline[13]; fd0[2]=clientline[14];fd0[3]=clientline[15];

fd0[4]=clientline[16]; fd0[5]=clientline[17]; fd0[6]=clientline[18];fd0[7]=clientline[19];

fd0[8]=clientline[20]; fd0[9]=clientline[21];fd0[10]=0;

}

if(strstr(clientline, "GET /temp?e=") !=0) // Распознаем 2-ю дату

{

fd1[0]=clientline[12]; fd1[1]=clientline[13]; fd1[2]=clientline[14]; fd1[3]=clientline[15];

fd1[4]=clientline[16]; fd1[5]=clientline[17]; fd1[6]=clientline[18]; fd1[7]=clientline[19];

fd1[8]=clientline[20]; fd1[9]=clientline[21]; fd1[10]=0;

}

if(strstr(clientline, "GET /temp?g=") !=0) // Распознаем 3-ю дату

{

fd2[0]=clientline[12]; fd2[1]=clientline[13]; fd2[2]=clientline[14]; fd2[3]=clientline[15];

fd2[4]=clientline[16]; fd2[5]=clientline[17]; fd2[6]=clientline[18]; fd2[7]=clientline[19];

fd2[8]=clientline[20]; fd2[9]=clientline[21]; fd2[10]=0;

}

if (strstr(clientline, "GET /temp") != 0) {

client.println("HTTP/1.1 200 OK");

client.println("Content-Type: text/html; charset=utf-8");

client.println();

client.print("<b>Температура вне= ");

sensors.requestTemperatures();

client.print(sensors.getTempCByIndex(0));

client.print(" град.<BR>");

client.print("Температура внутри= ");

client.print(ms5611.readTemperature());

client.print(" град.<BR>");

client.print("Влажность= ");

float hu = dht.readHumidity();

client.print(hu);

client.print(" %<BR>");

client.print("Давление= ");

client.print(0.007501*ms5611.readPressure());

client.print(" мм.рт.ст.</b><BR><br>");

client.print("Время на микроконтроллере= "); if(ho/10==0) client.print("0");

client.print(ho); client.print(":");

if(mi/10==0) client.print("0");

client.print(mi); client.print(" час.<BR>");

client.print("Дата на микроконтроллере= ");

if(endgraf==1 || fd0[0]==0 ) client.print("0000-00-00");

else if(day==0) client.print(fd0);

else if(day==1) client.print(fd1);

else if(day==2) client.print(fd2);

client.print("<br><br><b>Установленные даты:</b><br>");

client.print(fd0); client.print("<br>");

client.print(fd1); client.print("<br>");

client.print(fd2);

// client.print(fd0);

// Установка времени

client.print("<br><br><b>Установка времени:</b><BR>");

client.print("Формат: 0925 <BR>");

client.print("09-часы, 25-минуты <BR>");

client.print(FORM);

client.print("<INPUT type=\"text\" name=\"c\" value=\"\" size=10>");

client.print("<INPUT type=\"submit\" value=\"Установить\"> </FORM>");

// Установка даты

client.print("<br><b>Установка 3-х дат:</b><br>");

client.print("Формат: 2017-01-25 <BR>");

// client.print("<BR>");

client.print(FORM);

client.print("<INPUT type=\"text\" name=\"d\" value=\"\" size=10>");

client.print("<INPUT type=\"submit\" value=\"Установить\"> </FORM>");

client.print(FORM);

client.print("<INPUT type=\"text\" name=\"e\" value=\"\" size=10>");

client.print("<INPUT type=\"submit\" value=\"Установить\"> </FORM>");

client.print(FORM);

client.print("<INPUT type=\"text\" name=\"g\" value=\"\" size=10>");

client.print("<INPUT type=\"submit\" value=\"Установить\"> </FORM>");

client.print("<a href=\"/\">Перейти к главной странице</a>");

// Выходим с цикла while

break;

}

// Look for substring such as a request to get the root file

if (strstr(clientline, "GET / ") != 0) {

filename = rootFileName;

}

if (strstr(clientline, "GET /") != 0) {

// this time no space after the /, so a sub-file

if (!filename) filename = clientline + 5; // look after the "GET /" (5 chars)

// a little trick, look for the " HTTP/1.1" string and

// turn the first character of the substring into a 0 to clear it out.

(strstr(clientline, " HTTP"))[0] = 0;

// print the file we want

// Serial.println(filename);

myFile = SD.open(filename);

if (!myFile ) {

client.println("HTTP/1.1 404 Not Found");

client.println("Content-Type: text/html");

client.println();

client.println("<h2>File Not Found!</h2>");

break;

}

// Serial.println("Opened!");

client.println("HTTP/1.1 200 OK");

if (strstr(filename, ".htm") != 0)

client.println("Content-Type: text/html");

else if (strstr(filename, ".css") != 0)

client.println("Content-Type: text/css");

else if (strstr(filename, ".png") != 0)

client.println("Content-Type: image/png");

else if (strstr(filename, ".jpg") != 0)

client.println("Content-Type: image/jpeg");

else if (strstr(filename, ".gif") != 0)

client.println("Content-Type: image/gif");

else if (strstr(filename, ".3gp") != 0)

client.println("Content-Type: video/mpeg");

else if (strstr(filename, ".pdf") != 0)

client.println("Content-Type: application/pdf");

else if (strstr(filename, ".js") != 0)

client.println("Content-Type: application/x-javascript");

else if (strstr(filename, ".xml") != 0)

client.println("Content-Type: application/xml");

else

client.println("Content-Type: text");

client.println();

byte cB[1024];

int cC=0;

while (myFile.available())

{

cB[cC]=myFile.read();

cC++;

if(cC > 1023)

{

client.write(cB,1024);

cC=0;

}

}

if(cC > 0) client.write(cB,cC);

myFile.close();

} else {

// everything else is a 404

client.println("HTTP/1.1 404 Not Found");

client.println("Content-Type: text/html");

client.println();

client.println("<h2>File Not Found!</h2>");

}

break;

}

}

// give the web browser time to receive the data

delay(1);

client.stop();

}

// Serial.println("Free RAM: ");

// Serial.println(FreeRam());

}

}

void meter()

{

sensors.requestTemperatures();

cel=sensors.getTempCByIndex(0);

double cel1=ms5611.readTemperature();

double pre=0.007501*ms5611.readPressure();

float hu = dht.readHumidity();

lcd.setCursor(0, 0);

lcd.print("To=");

lcd.print(cel,1);

lcd.print(" Ti=");

lcd.print(cel1,1);

lcd.print("C ");

lcd.setCursor(0, 1);

lcd.print("P=");

lcd.print(pre,0);

lcd.print("mm");

lcd.print(" Hum=");

lcd.print(hu,0);

lcd.print("% ");

}

Здесь стандартная библиотека Ethernet изменена, так как приводила к зависаниям сервера.

Изменения:

Введены изменения в файл socket.cpp: ... uint16_t send(SOCKET s, const uint8_t * buf, uint16_t len) { uint8_t status=0; uint16_t ret=0; uint16_t freesize=0; if (len > W5100.SSIZE) ret = W5100.SSIZE; // check size not to exceed MAX size. else ret = len; // if freebuf is available, start. int timeout=0; Serial.print(" Packet 0"); // Замирает от сюда while (freesize < ret){ delay(1); freesize = W5100.getTXFreeSize(s); if (timeout++ > 1000) {close(s); delay(100);} status = W5100.readSnSR(s); if (status == SnSR::CLOSED ) { Serial.print(" Close "); return 0; } } // Замирает до сюда Serial.print(" Packet 1"); /* do { freesize = W5100.getTXFreeSize(s); status = W5100.readSnSR(s); if ((status != SnSR::ESTABLISHED) && (status != SnSR::CLOSE_WAIT)) { ret = 0; break; } } while (freesize < ret); */ // copy data W5100.send_data_processing(s, (uint8_t *)buf, ret); W5100.execCmdSn(s, Sock_SEND); /* while ( (W5100.readSnIR(s) & SnIR::SEND_OK) != SnIR::SEND_OK ) { if ( W5100.readSnSR(s) == SnSR::CLOSED ) { close(s); return 0; } } W5100.writeSnIR(s, SnIR::SEND_OK); */ return ret; } ... Изменены файлы EthernetClient.cpp, EthernetClient.h Добавлены функции: client.sockstat() client.getlen() client.disc() client.clos() client.sock1() EthernetClient.cpp: ... EthernetClient::EthernetClient(uint8_t sock) : _sock(sock) { } int EthernetClient::getlen(int s) { return W5100.getRXReceivedSize(s); } int EthernetClient::sock1() { return _sock; } void EthernetClient::disc(int i) { disconnect(i); } void EthernetClient::clos(int i) { close(i); } int EthernetClient::sockstat(int s) { return W5100.readSnSR(s) ; } ... EthernetClient.h: ... virtual void stop(); virtual int getlen(int s2); virtual int sock1(); virtual int sockstat(int s); virtual void disc(int ss); virtual void clos(int s1); virtual uint8_t connected(); ... Внесены изменение в функцию void EthernetClient::stop() - (client.stop()) для файла EthernetClient.cpp: void EthernetClient::stop() { if (_sock == MAX_SOCK_NUM) return; // attempt to close the connection gracefully (send a FIN to other side) disconnect(_sock); unsigned long start = millis(); Serial.print(status()); // wait a second for the connection to close // while (status() != SnSR::CLOSED && millis() - start < 1000) // delay(1); delay(300); Serial.print(status()); // if it hasn't closed, close it forcefully if (status() != SnSR::CLOSED) close(_sock); Serial.println(status());

На карте MicroSD в корневом каталоге должны быть записаны файлы:

1. index.htm

<HTML>

<HEAD>

<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">

<TITLE>Web server Arduino w5100</TITLE>

</HEAD>

<BODY>

<B><FONT color=blue size="5">Веб - сервер на Arduino mega<br> для просмотра температуры,<br> давления, влажности</FONT></B>

<hr>

<a href="web_gr.jpg"><img src="web3.JPG"/></a>

<hr>

<FONT size="4"><A href="in0.htm">График температуры вне помещения</A></FONT>

<br><FONT size="4"><A href="in1.htm">График температуры внутри помещения</A></FONT>

<br><FONT size="4"><A href="in2.htm">График атмосферного давления</A></FONT>

<br><FONT size="4"><A href="in3.htm">График влажности воздуха</A></FONT>

<br><FONT size="4"><A href="temp">Текущие показания датчиков, установки</A></FONT>

</body>

</html>

2. in0.htm

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">

<script type="text/javascript"

src="dygm.js"></script>

<link rel="stylesheet" href="dygraph.css">

</head>

<body>

<div id="graphdiv2"

style="width:800px; height:600px;"></div>

<script type="text/javascript">

g2 = new Dygraph(

document.getElementById("graphdiv2"),

"DATAT.TXT", // path to CSV file

{

title: 'Температура вне помещения',

xlabel: 'Дата',

ylabel: 'Температура град. С',

showRoller: true,

rollPeriod: 5,

//color: "#0000ff",

labels: [ "Date", "Temp" ],

strokeWidth: 1.5

}

);

</script>

<br>

<A href="/">Возврат к главной странице</A>

</body>

</html>

3. in1.htm

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">

<script type="text/javascript"

src="dygm.js"></script>

<link rel="stylesheet" href="dygraph.css" />

</head>

<body>

<div id="graphdiv4"

style="width:800px; height:600px;"></div>

<script type="text/javascript">

g4 = new Dygraph(

document.getElementById("graphdiv4"),

"datata.txt",

{

title: 'Температура внутри помещения',

xlabel: 'Дата',

ylabel: 'Температура град. С',

showRoller: true,

rollPeriod: 5,

//color: "#0000ff",

labels: [ "Date", "Temp" ],

strokeWidth: 1.5

}

);

</script>

<br>

<A href="/">Возврат к главной странице</A>

</body>

</html>

4. in2.htm

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">

<script type="text/javascript"

src="dygm.js"></script>

<link rel="stylesheet" href="dygraph.css" />

</head>

<body>

<div id="graphdiv4"

style="width:800px; height:600px;"></div>

<script type="text/javascript">

g4 = new Dygraph(

document.getElementById("graphdiv4"),

"datap.txt",

{

title: 'Атмосферное давление',

xlabel: 'Дата',

ylabel: 'Давление в мм.рт.ст.',

showRoller: true,

rollPeriod: 5,

//color: "#0000ff",

labels: [ "Date", "Press" ],

strokeWidth: 1.5

}

);

</script>

<br>

<A href="/">Возврат к главной странице</A>

</body>

</html>

5. in3.htm

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">

<script type="text/javascript"

src="dygm.js"></script>

<link rel="stylesheet" href="dygraph.css" />

</head>

<body>

<div id="graphdiv5"

style="width:800px; height:600px;"></div>

<script type="text/javascript">

g4 = new Dygraph(

document.getElementById("graphdiv5"),

"datah.txt",

{

title: 'Влажность воздуха',

xlabel: 'Дата',

ylabel: 'Влажность воздуха в %',

showRoller: true,

rollPeriod: 5,

//color: "#0000ff",

labels: [ "Date", "Hum" ],

strokeWidth: 1.5

}

);

</script>

<br>

<A href="/">Возврат к главной странице</A>

</body>

</html>

6. dygraph.min.js - файл библиотеки dygraphs (переименован в dygm.js, так как имя файла не более 8 символов, расширение - не более 3-х символов в файловой системе FAT)

7. dygraph.gss - файл библиотеки dygraphs (переименован в dygraph.css)

8. Файлы данных показаний датчиков (опрос выполнялся каждые 300сек.)

22.01.2018г.