• Уважаемые посетители сайта esp8266.ru!
    Мы отказались от размещения рекламы на страницах форума для большего комфорта пользователей.
    Вы можете оказать посильную поддержку администрации форума. Данные средства пойдут на оплату услуг облачных провайдеров для сайта esp8266.ru
  • Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Рисуем графики

Памяти у нас много, флэш тоже. Поэтому мы можем изгаляться как хотим.
Тема урока: графики.
Есть такая штука для рисования графиков - http://www.highcharts.com/
Все достаточно просто, но вот примеров простых(!) не хватает. Все бы им PHP, MySQL, и прочие навороты. Но у нас то этого барахла нет ! А красивый график показаний охота.

А пожалуйста !

В примере 2 типа основных графиков - chart и stock.
Странички в виде констант в PROGMEM.
Так же пример работы ntp, потому как график показаний без реального времени фигня.

Разбираемся, комментируем ...

Код:
#include <FS.h>                   //this needs to be first, or it all crashes and burns...
#include <ESP8266WiFi.h>          //https://github.com/esp8266/Arduino
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiUdp.h>
#include <WiFiManager.h>          //https://github.com/tzapu/WiFiManager
#include <time.h>

#define debug true

ESP8266WebServer server(80);
int co2a[60];
long co2t[60];

const char PAGE_stock[] PROGMEM = R"=====(
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <script type="text/javascript" src="//code.jquery.com/jquery-1.9.1.js"></script>
  <title>Stock</title>
<script type='text/javascript'>//<![CDATA[
$(function () {
    $.getJSON('data.json', function (data) {
        $('#container').highcharts('StockChart', {
            rangeSelector : {
                selected : 1
            },
            title : {
                text : 'CO2PPM'
            },
            series : [{
                name : 'CO2:',
                data : data,
                tooltip: {
                    valueDecimals: 0
                }
            }]
        });
    });
});
//]]>
</script>
</head>
<body>
<script src="https://code.highcharts.com/stock/highstock.js"></script>
<script src="https://code.highcharts.com/stock/modules/exporting.js" type="text/javascript"></script>
<div id="container" style="height: 400px; min-width: 310px"></div>
<a href="/chart.html">Chart</a>
</body>
</html>)=====";

const char PAGE_chart[] PROGMEM = R"=====(
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Chart</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<script type='text/javascript'>//<![CDATA[
$(document).ready(function() {
    var options = {
        chart: {
            renderTo: 'container',
            type: 'spline'
        },
        title: {
            text: 'CO2'
        },
        xAxis: {
            type: 'datetime'
        },
        yAxis: {
          title: {
            text: 'CO2 (PPM)'
                }
        },
        plotOptions: {
            line: {
                dataLabels: {
                    enabled: true
                },
                enableMouseTracking: true
            }
        },
            series: [{
            type: 'spline',
            name: 'CO2 Sensor data',
            tooltip: {
            valueDecimals: 0,
            valueSuffix: 'PPM'
            }
        }]
    };
    $.getJSON('data.json', function(data) {
        options.series[0].data = data;
        var chart = new Highcharts.Chart(options);
    });
});
//]]>
</script>
</head>
   <body>
<script src="https://code.highcharts.com/stock/highstock.js" type="text/javascript"></script>
<script src="https://code.highcharts.com/stock/modules/exporting.js" type="text/javascript"></script>
<div id="container" style="height: 400px; min-width: 310px"></div>
<a href="/stock.html">Stock</a>
</body>
</html>
)=====";

void wifimanstart() { // Волшебная процедура начального подключения к Wifi.
                      // Если не знает к чему подцепить - создает точку доступа ESP8266 и настроечную таблицу http://192.168.4.1
                      // Подробнее: https://github.com/tzapu/WiFiManager
  WiFiManager wifiManager;
  wifiManager.setDebugOutput(debug);
  wifiManager.setMinimumSignalQuality();
  if (!wifiManager.autoConnect("ESP8266")) {
  if (debug) Serial.println("failed to connect and hit timeout");
    delay(3000);
    //reset and try again, or maybe put it to deep sleep
    ESP.reset();
    delay(5000); }
if (debug) Serial.println("connected...");
}

void processCSV()
{       
    String str = "[\n";
    for (int i=0; i<=59; i++) {
      if ((i>0)&&(String(co2t[i])!="0")) str +=",\n";
      if (String(co2t[i])!="0") str += "["+String(co2t[i])+"000,"+String(co2a[i])+"]";}
      str +="\n]";
    server.send ( 200, "application/json", str);  
}

void ServerInit() {
  server.on ( "/", []() { server.send ( 200, "text/html", PAGE_chart ); });
  server.on ( "/chart.html", []() { server.send ( 200, "text/html", PAGE_chart ); });
  server.on ( "/stock.html", []() { server.send ( 200, "text/html", PAGE_stock ); });
  server.on ( "/data.json", processCSV  );
     server.onNotFound ( []() { Serial.println("Page Not Found"); server.send ( 400, "text/html", "Page not Found" );   }  );
    server.begin();
}

int timezone = 3;
int dst = 0;
time_t now;

void setup() {
  Serial.begin(115200);
  wifimanstart();
  configTime(timezone * 3600, 0, "pool.ntp.org", "time.nist.gov");
    if (debug) Serial.print("\nWaiting for time");
    while (!time(nullptr)) { if (debug) Serial.print("."); delay(200);} if (debug) Serial.println("");
  now = time(nullptr);
  Serial.print("Unix time:");Serial.println(now);
  Serial.println(ctime(&now));
  for (int i=0; i<=59; i++) {co2t[i]=(now-3600)+(60*i); co2a[i]=random(100,1000);} //co2t - datetime измерения в формате unixtime, co2a - значение измерения

  ServerInit();
  Serial.print("http://"); Serial.print(WiFi.localIP());Serial.println("/");
 
}
  void loop() {
 
    server.handleClient();
    yield();

  }
 

romanko

New member
Не всегда есть возможность использовать сторонние js, например нет доступа к интернету.
Использую http://dygraphs.com/
Если использовать кеширование и сжатие, то можно обойтись средствами контролера esp8266
Кеширование скрипта
Код:
server.serveStatic("/dygraph-combined.js",   SPIFFS, "/dygraph-combined.js"  ,"max-age=86400");
Файл dygraph-combined.js сжимаем gz, получается 37 кБт - время считывания файла первый раз около секунды потом берется из кеша браузера
Данные тоже пишу в файл флеш памяти.
Код:
f = SPIFFS.open(datafile, "a");
........
f.println(str);
Посуточный графики за неделю с шагом 5 минут помещаются в флеш памяти без проблем.
 
Не всегда есть возможность использовать сторонние js, например нет доступа к интернету.
Использую http://dygraphs.com/
...
Посуточный графики за неделю с шагом 5 минут помещаются в флеш памяти без проблем.
Пример в студию. :)

Ну у меня комплект js скриптов в 100К помещается, тоже можно на ESP-шку сложить. И тоже будет локально работать.
Мой пример сделан для того чтобы можно его было скопипастить, скомпилить и зашить. И чтобы сразу заработало :)

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

romanko

New member
Это функция отдачи с сервера страницы с графиком -
Код:
void setup(void){
......
server.on ( "/graph.htm", []() {
if (server.args() > 0 ) getday=server.arg(0).toInt(); else getday=0;
server.send ( 200, "text/html", PAGE_Graph(getday));
} );
.....
}


String PAGE_Graph(int gd=0) {
    String begin, fname, end, result;
    int selday;
    if (DateTime.wday > gd) {
     selday=DateTime.wday-gd;
    } else {
     selday=DateTime.wday+7-gd;   
    }
    begin="<!DOCTYPE html><html><head><meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"><meta name=\"viewport\" content=\"width=device-width\"/><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><title>Графік</title><script type=\"text/javascript\" src=\"dygraph-combined.js\"></script></head><body><div style=\"max-width:650px;\" class=\"btn btn--m\"><a class=\"wday\" href=\"/\"><< Home</a> <a class=\"wday\" href=\"/graph.htm?d=5\">-5днів</a> <a class=\"wday\" href=\"/graph.htm?d=4\">-4дня</a> <a class=\"wday\" href=\"/graph.htm?d=3\">-3дня</a> <a class=\"wday\" href=\"/graph.htm?d=2\">-2дня</a> <a class=\"wday\" href=\"/graph.htm?d=1\">Вчора</a> <a class=\"wday\" href=\"/graph.htm\">Сьогодні</a></div><br><div id=\"graphdiv2\" style=\"max-width:650px; width:100%; height:300px;\"></div><br><div>Power=(Вимкнено-0/Увімкнено-5); Auto=(Ручний-0/Автоматичний режим-10)</div><script type=\"text/javascript\"> g2 = new Dygraph(document.getElementById(\"graphdiv2\"), \"gr_";
    fname=(String)selday+".txt";
    end="\", {series: {Zavd: {stepPlot: true}, Temp: {stepPlot: false}, Power: {stepPlot: true}, Auto: {stepPlot: true}, Kotel: {stepPlot: true}}, fillGraph: false, stackedGraph: false, includeZero: true} ); </script></body></html>";
    result = begin+fname+end;
    return result;
}
это запись данных в файл данных для рисования графика
Код:
    String datafile="/gr_"+ (String)DateTime.wday+".txt";    
    if (!SPIFFS.exists(datafile)) {
        String grt="";
        f = SPIFFS.open(datafile, "w");
        if (config.pwm_stan) grt=",Kotel";
        f.println("Datetime,Auto,Power,Zavd,Temp"+grt);
    } else {
        f = SPIFFS.open(datafile, "a");
        String send_pow="0", send_avt="0";
        String str =FullDate()+" "+FullTime()+",";
        if (config.automat) send_avt="10";
        if (config.power) send_pow="5";
        str += send_avt+","+send_pow+",";
        str +=(String)zavd_temp+","+(String)real_temp;
        if (config.pwm_stan) str+=","+(String)(real_pwm*10+30);
        f.println(str);
    }
    f.close();
Это куски кода с рабочего проекта. Плюс кеширование и сжатие файла библиотеки /dygraph-combined.js описывал выше.
Решил пойти таким путем как раз что б отказаться от использования сторонних серверов.
 

pvvx

Активный участник сообщества
Озадачившись информативными графиками на автономном устройстве, после долгих переборов вариантов, вышло что для AJAX проще использовать генерацию XML и отображение к примеру с помощью уже готовых примитивов типа от fusioncharts, загружаемых с устройства.
Вот их примеры: http://www.fusioncharts.com/charts/msline_1/
Генераторы XML по скриптам/шаблонам очень легко встраиваются в устройство. Примеры уже есть в Web-свалке.
 

pvvx

Активный участник сообщества
Рисует графики в реальном времени.
А осциллограф на java или ещё как есть? Web у меня дает UDP поток со встроенного ADC в ESP до 192 кГц, но как отобразить на странице?
Web-flash бы какой для примеру с осциллографом в реал-тайм... а формат потока данных изменю без проблем. Java ужасно тормозит...
На Ардуине никаких приличных Web не сделать - там ужасная и тормозная файловая система Spiffs. :(
Ещё очень хотчется решение на java или другим путем загрузки со встроенного в esp-web отображения 3D датчиков. Сейчас сделал драйвер в ESP для 10-DOF (акселерометр, гироскоп, магнитометр, термометр, давление) с дополнительными хорошими датчиками влажности, освещенности, ИФК, кислородом... для ESP8266 с дискретностью в 100Гц на все точки и требуется отображение всего этого в реальном времени и 3D, а так-же статистики (усреднений по ним) по циклу за год с шагом точек на 5..15 минут, сохраняемыми в ESP с flash с 16 Мегабайт. :)
 
Последнее редактирование:

LeshaEzGaming

New member
Для графиков в реальном времени как аналог из примера посоветовал бы smoothiecharts. Сжатая Google Closure Compiler'ом весит 8.5кбайт, в gz 2.8кб.
 

pvvx

Активный участник сообщества
Для графиков в реальном времени как аналог из примера посоветовал бы smoothiecharts. Сжатая Google Closure Compiler'ом весит 8.5кбайт, в gz 2.8кб.
ina219grf.gif
При менее 10 ms на точку возникают какие-то странности... квадратные... А модуль ESP8266 (web-свалка) через websocket дает точки с шагом в 1..2 ms.
Ещё скачет масштабирование по всему телу графика. Надо переделывать автоустановку min/max в smoothie.js...

Попробовал без задержек:
Ina219_0.5Hz.gif
Клетка графика 100 ms, с генератора на INA219 идет синус 0.5Гц.
Модуль ESP-01 опрашивает датчик INA219 пo I2C по одному значению тока или напряжения через WebSocket в текстовом виде. За секунду по соединению проходит 510 опросов значений (отображено как samplerate). Вывод графика тормозит опрос в javascript - без графика опрос через WebSocket значений от датчика больше 850 раз в сек (упирается в скорость I2С)... :(
:
Никакой оптимизации, чисто для теста скорости websocket и smoothie.js
JavaScript:
<script type="text/javascript">
var line1 = new TimeSeries();
var line2 = new TimeSeries();
var smoothie1 = new SmoothieChart({ millisPerPixel: 3, maxValueScale:1.05,
  grid: { strokeStyle:'rgb(75, 75, 75)', lineWidth: 1, millisPerLine: 100, verticalSections: 6 },
  labels: { fillStyle:'rgb(255, 255, 255)' }
});
var smoothie2 = new SmoothieChart({ millisPerPixel: 3, maxValueScale:1.05,
  grid: { strokeStyle:'rgb(75, 75, 75)', lineWidth: 1, millisPerLine: 100, verticalSections: 6 },
  labels: { fillStyle:'rgb(255, 255, 255)' }
});
smoothie1.addTimeSeries(line1, { strokeStyle:'rgb(0, 255, 0)',  lineWidth: 2 });
smoothie2.addTimeSeries(line2, { strokeStyle:'rgb(255, 0, 255)', lineWidth: 2 });
smoothie1.streamTo(document.getElementById("mycanvas1"));
smoothie2.streamTo(document.getElementById("mycanvas2"));
var uicnt = 0;
var smprate = 0;
function onMessage(evt) {
    smprate++;
    if (evt.data) {
       if(uicnt) {
            newval = (eval(evt.data)*0.0964).toFixed(2);    
            line2.append(new Date().getTime(), newval);
            document.getElementById('xdata2').innerHTML = newval;
        }
        else {
            newval = (eval(evt.data)*0.000519).toFixed(4);    
            line1.append(new Date().getTime(), newval);
            document.getElementById('xdata1').innerHTML = newval;
        }
    }
    wsSendMsg();
}
var wsUri = "ws://192.168.4.1/web.cgi";
// var wsUri = "ws://"+window.location.host.toString()+"/web.cgi";
function testWebSocket() {
    websocket = new WebSocket(wsUri);
    websocket.onopen = function(evt) {onOpen(evt)};
    websocket.onmessage = function(evt) {onMessage(evt)};
}
function wsSendMsg() {
    if(uicnt) {uicnt = 0; websocket.send("ovl$3"); }
    else {uicnt = 1; websocket.send("ovl$4"); }
}
function onOpen(evt) {
    websocket.send("ovl$=2");
    wsSendMsg();
}
var wstt;
function wsSmpRate() {
    document.getElementById('smprate').innerHTML = smprate;
    smprate = 0;
    wstt = setTimeout(wsSmpRate, 1000);
}
wstt = setTimeout(wsSmpRate, 1000);
testWebSocket();
</script>
Примерно так это выглядит:
 
Последнее редактирование:
Сверху Снизу