- 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г.