Skip to content

Instantly share code, notes, and snippets.

@panda728
Last active March 26, 2024 22:53
Show Gist options
  • Save panda728/01a314b66890d8060b61efaef4b272ee to your computer and use it in GitHub Desktop.
Save panda728/01a314b66890d8060b61efaef4b272ee to your computer and use it in GitHub Desktop.
M5Stackでスクロールグラフ(M5GFX ver.)(Core2で動作確認)
#include <Arduino.h>
#include <M5GFX.h>
#include <vector>
#include <map>
#include <float.h>
#include <time.h>
static float room1[] = {21.6, 21.6, 21.6, 21.6, 21.6, 21.5, 21.5, 21.5, 21.5, 21.5, 21.5, 21.5, 21.5, 21.5, 21.5, 21.4, 21.4, 21.4, 21.4, 21.4, 21.4, 21.4, 21.4, 21.4, 21.4, 21.4, 21.4, 21.3, 21.3, 21.3, 21.3, 21.3, 21.3, 21.3, 21.3, 21.3, 21.2, 21.2, 21.2, 21.2, 21.2, 21.2, 21.2, 21.2, 21.2, 21.2, 21.2, 21.2, 21.1, 21.1, 21.1, 21.1, 21.1, 21.1, 21.1, 21.1, 21.1, 21.1, 21.1, 21.1, 21.0, 21.0, 21.0, 21.0, 21.0, 21.0, 21.0, 21.0, 21.0, 21.0, 21.0, 21.0, 21.0, 21.0, 21.0, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.8, 20.8, 20.8, 20.8, 20.8, 20.8, 20.8, 20.8, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 20.9, 21.0, 21.0, 21.1, 21.1, 21.1, 21.2, 21.3, 21.4, 21.4, 21.5, 21.6, 21.7, 21.9, 22.0, 22.1, 22.1, 22.1, 22.1, 22.1, 22.2, 22.3, 22.5, 22.6, 22.7, 22.8, 22.7, 22.7, 22.7, 22.6, 22.6, 22.6, 22.5, 22.5, 22.5, 22.5, 22.6, 22.7, 22.8, 22.9, 22.9, 23.0, 23.1, 23.2, 23.2, 23.2, 23.3, 23.3, 23.4, 23.4, 23.4, 23.5, 23.5, 23.6, 23.6, 23.7, 23.7, 23.8, 23.9, 23.9, 24.0, 24.1, 24.2, 24.2, 24.2, 24.3, 24.4, 24.4, 24.4, 24.5, 24.5, 24.6, 24.6, 24.7, 24.7, 24.8, 24.8, 24.9, 24.9, 24.9, 24.9, 25.0, 25.0, 25.1, 25.2, 25.2, 25.2, 25.2, 25.3, 25.3, 25.4, 25.4, 25.5, 25.5, 25.5, 25.6, 25.6, 25.7, 25.7, 25.8, 25.8, 25.8, 25.8, 25.9, 25.9, 25.9, 26.0, 26.0, 26.0, 26.1, 26.1, 26.1, 26.2, 26.2, 26.2, 26.3, 26.3, 26.4, 26.4, 26.4, 26.4, 26.4, 26.5, 26.5, 26.5, 26.6, 26.6, 26.6, 26.6, 26.7, 26.7, 26.7, 26.7, 26.7, 26.8, 26.8, 26.8, 26.9, 26.9, 26.9, 27.0, 27.0, 27.1, 27.1, 27.1, 27.2, 27.2, 27.3, 27.3, 27.3, 27.3, 27.3, 27.3, 27.3, 27.4, 27.4, 27.4, 27.4, 27.4, 27.5, 27.5, 27.5, 27.5, 27.5, 27.5, 27.5, 27.5, 27.5, 27.5, 27.5, 27.4, 27.4, 27.5, 27.5, 27.5, 27.6, 27.6, 27.6, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.8, 27.8, 27.8, 27.8, 27.8, 27.8, 27.8, 27.8, 27.8, 27.8, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.6, 27.6, 27.6, 27.6, 27.6, 27.5, 27.5, 27.5, 27.5, 27.4, 27.4, 27.4, 27.4, 27.3, 27.3, 27.3, 27.3, 27.3, 27.2, 27.2, 27.2, 27.1, 27.1, 27.1, 27.0, 27.0, 27.0, 27.0, 26.9, 26.9, 26.9, 26.9, 26.9, 26.8, 26.8, 26.8, 26.7, 26.7, 26.7, 26.6, 26.6, 26.5, 26.5, 26.5, 26.4, 26.4, 26.4, 26.4, 26.4, 26.4, 26.3, 26.3, 26.2, 26.1, 26.0, 25.9, 25.9, 25.8, 25.7, 25.7, 25.6, 25.6, 25.5, 25.5, 25.5, 25.4, 25.4, 25.4, 25.4, 25.3, 25.3, 25.3, 25.2, 25.2, 25.1, 25.1, 25.0, 25.0, 24.8, 24.7, 24.6, 24.5, 24.4, 24.4, 24.4, 24.4, 24.4, 24.3, 24.3, 24.2, 24.2, 24.1, 24.1, 24.0, 24.0, 24.0, 24.1, 24.1, 24.2, 24.2, 24.2, 24.2, 24.3, 24.3, 24.3, 24.3, 24.3, 24.3, 24.3, 24.3, 24.3, 24.3, 24.3, 24.3, 24.3, 24.2, 24.2, 24.2, 24.2, 24.2, 24.2, 24.2, 24.2, 24.2, 24.2, 24.2, 24.2, 24.2, 24.1, 24.1, 24.1, 24.1, 24.1, 24.0, 24.0, 24.0};
static float room2[] = {23.1, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 23.0, 22.9, 22.9, 22.9, 22.9, 22.9, 22.9, 22.9, 22.9, 22.9, 22.9, 22.9, 22.9, 22.8, 22.8, 22.8, 22.8, 22.8, 22.8, 22.8, 22.8, 22.8, 22.8, 22.8, 22.8, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.7, 22.6, 22.6, 22.6, 22.6, 22.6, 22.6, 22.6, 22.5, 22.5, 22.5, 22.5, 22.5, 22.5, 22.5, 22.5, 22.5, 22.4, 22.4, 22.4, 22.4, 22.4, 22.4, 22.4, 22.4, 22.4, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.2, 22.3, 22.2, 22.2, 22.3, 22.2, 22.2, 22.2, 22.2, 22.2, 22.2, 22.2, 22.2, 22.2, 22.2, 22.2, 22.2, 22.2, 22.2, 22.1, 22.1, 22.1, 22.1, 22.1, 22.1, 22.1, 22.0, 22.1, 22.1, 22.0, 22.0, 22.0, 22.0, 22.0, 22.0, 22.0, 22.0, 22.0, 22.0, 22.0, 22.0, 22.0, 22.0, 22.0, 22.0, 22.1, 22.1, 22.1, 22.1, 22.2, 22.2, 22.2, 22.2, 22.2, 22.2, 22.2, 22.2, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.3, 22.4, 22.4, 22.4, 22.4, 22.5, 22.5, 22.5, 22.5, 22.5, 22.6, 22.6, 22.6, 22.7, 22.7, 22.7, 22.7, 22.8, 22.8, 22.8, 22.8, 22.9, 22.9, 22.9, 23.0, 23.0, 23.0, 23.1, 23.1, 23.2, 23.2, 23.2, 23.3, 23.3, 23.3, 23.4, 23.4, 23.5, 23.5, 23.5, 23.6, 23.6, 23.7, 23.7, 23.7, 23.8, 23.8, 23.8, 23.9, 23.9, 23.9, 24.0, 24.0, 24.1, 24.2, 24.2, 24.2, 24.3, 24.3, 24.3, 24.4, 24.4, 24.4, 24.4, 24.5, 24.5, 24.5, 24.6, 24.6, 24.7, 24.7, 24.7, 24.7, 24.8, 24.8, 24.8, 24.8, 24.9, 24.9, 24.9, 25.0, 25.0, 25.0, 25.1, 25.1, 25.1, 25.2, 25.2, 25.2, 25.3, 25.3, 25.3, 25.3, 25.3, 25.3, 25.4, 25.4, 25.4, 25.5, 25.5, 25.6, 25.6, 25.7, 25.7, 25.8, 25.8, 25.8, 25.9, 26.0, 26.0, 26.0, 26.0, 26.0, 26.1, 26.1, 26.1, 26.1, 26.2, 26.2, 26.2, 26.2, 26.2, 26.2, 26.3, 26.3, 26.3, 26.3, 26.3, 26.3, 26.4, 26.4, 26.4, 26.4, 26.4, 26.4, 26.4, 26.5, 26.5, 26.5, 26.6, 26.6, 26.6, 26.6, 26.6, 26.7, 26.7, 26.7, 26.7, 26.8, 26.8, 26.8, 26.8, 26.8, 26.8, 26.9, 26.9, 26.9, 26.9, 26.9, 26.9, 26.9, 26.9, 26.9, 27.0, 26.9, 27.0, 27.0, 27.0, 27.0, 27.0, 26.9, 26.9, 26.9, 26.9, 26.9, 26.9, 26.9, 26.9, 26.9, 26.9, 26.8, 26.8, 26.8, 26.8, 26.8, 26.8, 26.8, 26.8, 26.7, 26.7, 26.7, 26.7, 26.7, 26.6, 26.6, 26.6, 26.5, 26.5, 26.4, 26.4, 26.4, 26.4, 26.3, 26.3, 26.3, 26.3, 26.3, 26.3, 26.2, 26.2, 26.2, 26.2, 26.2, 26.1, 26.1, 26.1, 26.1, 26.1, 26.0, 26.0, 26.0, 26.0, 26.0, 25.9, 25.9, 25.9, 25.9, 25.9, 25.8, 25.8, 25.8, 25.8, 25.8, 25.8, 25.8, 25.8, 25.7, 25.7, 25.7, 25.7, 25.7, 25.7, 25.7, 25.7, 25.7, 25.6, 25.6, 25.6, 25.6, 25.6, 25.6, 25.6, 25.6, 25.5, 25.5, 25.5, 25.5, 25.5, 25.5, 25.5, 25.5, 25.5, 25.5, 25.4, 25.4, 25.4, 25.4, 25.4, 25.4, 25.4, 25.4, 25.3, 25.3, 25.3, 25.3, 25.3, 25.3, 25.3, 25.3, 25.3, 25.3, 25.3, 25.3, 25.3, 25.2, 25.2, 25.2, 25.2, 25.2, 25.2, 25.2, 25.1, 25.1, 25.1, 25.1, 25.1, 25.1, 25.1, 25.1, 25.1, 25.1, 25.0, 25.0, 25.0, 25.0, 25.0, 24.9, 24.9, 24.9};
static float room3[] = {24.5, 24.4, 24.4, 24.4, 24.3, 24.3, 24.3, 24.3, 24.2, 24.2, 24.2, 24.2, 24.2, 24.1, 24.1, 24.1, 24.1, 24.1, 24.1, 24.0, 24.0, 24.0, 24.0, 24.0, 24.0, 23.9, 23.9, 23.9, 23.8, 23.8, 23.8, 23.8, 23.8, 23.8, 23.8, 23.8, 23.8, 23.7, 23.7, 23.7, 23.7, 23.7, 23.7, 23.7, 23.7, 23.7, 23.7, 23.7, 23.7, 23.7, 23.6, 23.6, 23.6, 23.6, 23.6, 23.6, 23.6, 23.6, 23.6, 23.5, 23.5, 23.5, 23.5, 23.5, 23.5, 23.5, 23.5, 23.5, 23.5, 23.5, 23.5, 23.5, 23.5, 23.4, 23.4, 23.4, 23.4, 23.4, 23.4, 23.4, 23.4, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.2, 23.2, 23.2, 23.2, 23.2, 23.2, 23.2, 23.2, 23.2, 23.2, 23.2, 23.2, 23.2, 23.2, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.3, 23.4, 23.4, 23.4, 23.4, 23.4, 23.5, 23.5, 23.5, 23.5, 23.5, 23.5, 23.5, 23.4, 23.4, 23.4, 23.4, 23.5, 23.5, 23.5, 23.5, 23.5, 23.5, 23.6, 23.6, 23.6, 23.6, 23.6, 23.6, 23.6, 23.6, 23.6, 23.6, 23.6, 23.6, 23.7, 23.7, 23.7, 23.7, 23.7, 23.7, 23.7, 23.8, 23.8, 23.8, 23.8, 23.8, 23.8, 23.9, 23.9, 23.9, 24.0, 24.0, 24.0, 24.0, 24.0, 24.1, 24.1, 24.1, 24.1, 24.2, 24.2, 24.2, 24.2, 24.3, 24.3, 24.3, 24.4, 24.4, 24.4, 24.5, 24.5, 24.5, 24.6, 24.6, 24.7, 24.7, 24.7, 24.8, 24.8, 24.8, 24.9, 24.9, 25.0, 25.0, 25.1, 25.1, 25.1, 25.2, 25.2, 25.3, 25.3, 25.3, 25.3, 25.4, 25.4, 25.5, 25.5, 25.5, 25.5, 25.6, 25.6, 25.7, 25.7, 25.7, 25.8, 25.8, 25.8, 25.8, 25.9, 25.9, 25.9, 26.0, 26.0, 26.0, 26.0, 26.1, 26.1, 26.1, 26.2, 26.2, 26.2, 26.2, 26.3, 26.3, 26.3, 26.3, 26.3, 26.3, 26.4, 26.4, 26.4, 26.4, 26.5, 26.5, 26.5, 26.5, 26.6, 26.6, 26.7, 26.7, 26.7, 26.7, 26.7, 26.8, 26.8, 26.8, 26.8, 26.8, 26.9, 26.9, 26.9, 27.0, 27.0, 27.0, 27.0, 27.1, 27.1, 27.1, 27.1, 27.2, 27.2, 27.2, 27.2, 27.2, 27.2, 27.2, 27.2, 27.2, 27.3, 27.3, 27.3, 27.3, 27.3, 27.3, 27.4, 27.4, 27.4, 27.4, 27.4, 27.5, 27.5, 27.5, 27.5, 27.5, 27.5, 27.5, 27.6, 27.6, 27.6, 27.6, 27.6, 27.6, 27.6, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.7, 27.6, 27.6, 27.6, 27.6, 27.5, 27.5, 27.5, 27.5, 27.5, 27.5, 27.5, 27.4, 27.4, 27.3, 27.3, 27.3, 27.3, 27.3, 27.3, 27.3, 27.3, 27.3, 27.2, 27.2, 27.2, 27.2, 27.2, 27.2, 27.2, 27.2, 27.1, 27.1, 27.1, 27.1, 27.1, 27.1, 27.1, 27.0, 27.0, 27.0, 27.0, 27.0, 26.9, 26.9, 26.9, 26.9, 26.8, 26.8, 26.9, 26.9, 26.9, 27.0, 27.0, 27.0, 27.0, 27.0, 27.0, 27.1, 27.2, 27.2, 27.2, 27.1, 27.1, 27.1, 27.1, 27.1, 27.1, 27.0, 27.0, 26.9, 26.8, 26.8, 26.8, 26.7, 26.7, 26.7, 26.7, 26.7, 26.6, 26.6, 26.6, 26.6, 26.6, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.5, 26.4, 26.4, 26.3, 26.3, 26.3, 26.3, 26.3, 26.3, 26.3, 26.4, 26.4, 26.4, 26.3, 26.3, 26.3, 26.3, 26.3, 26.3, 26.3, 26.3, 26.2, 26.2, 26.2, 26.2, 26.2};
constexpr std::uint_fast8_t LINE_COUNT = 6;
constexpr std::int32_t GRAPH_WIDTH = 300; // (1px=1min)
static M5GFX lcd;
static LGFX_Sprite sprites[2](&lcd);
static std::uint_fast8_t flip = 0;
static std::int_fast16_t spriteHeight = 80;
static std::int32_t lcdWidth;
static std::int32_t lcdHeight;
static std::int32_t loopCounter = 0;
static std::int32_t totalDataSize = 0;
static std::int32_t sec, psec;
static std::int32_t fps = 0, frame_count = 0;
namespace GraphUtil
{
struct axisParam
{
float max;
float step;
float range;
};
float calcStep(float rangeY)
{
if (rangeY <= 5)
return 1;
if (rangeY <= 10)
return 2;
if (rangeY <= 15)
return 3;
if (rangeY <= 25)
return 5;
if (rangeY <= 50)
return 10;
return 12;
}
axisParam getAxisRange(float dataMin, float dataMax, std::uint_fast8_t divideCount)
{
axisParam param;
param.step = calcStep(std::ceil(dataMax) - std::floor(dataMin));
for (int i = 0; i < 10; i++)
{
param.range = param.step * divideCount;
float dataAvg = std::floor((dataMax + dataMin) / 2);
param.max = dataAvg + (param.step * 3);
if (param.max > dataMax && param.max - param.range < dataMin)
return param;
param.step = calcStep(std::ceil(dataMax * (1 + 0.1 * i)) - std::floor(dataMin));
}
param.step = calcStep(FLT_MAX);
param.range = param.step * divideCount;
param.max = std::ceil(dataMax);
return param;
}
}
namespace Logger
{
std::int32_t maxLogCount = GRAPH_WIDTH; // graphWidth
struct metricsLog
{
time_t time;
std::string address;
std::string name;
float temperature;
float humidity;
float battery;
float rssi;
bool operator<(const metricsLog &another) const
{
return name < another.name;
}
};
struct logData
{
time_t time;
float temperature[LINE_COUNT];
float humidity[LINE_COUNT];
};
std::map<time_t, logData> logs;
// Remember whether the name of the measurement log is the nth in the graph.
std::map<std::string, std::int8_t> keys;
std::int8_t getKeyIndex(std::string key)
{
auto iter = keys.find(key);
if (iter != keys.end())
{
return iter->second;
}
std::int8_t i = keys.size();
if (i > 6)
i = -1;
keys.insert(std::make_pair(key, i));
return i;
}
void push(std::vector<metricsLog> &mLogs)
{
// Extract the value of temp from the measurement log and record it in the data for graphs (logs)
// Prerequisite.
// Data of newer time than the registered data (logs) is entered.
// For ease of recording in CSV, the logData type is in the format {time, measured value x6}.
// If the measured value is FLT_MAX, it becomes invalid data and the point is not drawn.
// Time is rounded to the nearest minute and stored (1px=1min)
// If there is data with the same time after rounding, it will be overwritten
// If the number of logs exceeds the maxLogCount, delete the oldest one.
sort(mLogs.begin(), mLogs.end());
for (auto itr = mLogs.begin(); itr != mLogs.end(); ++itr)
{
auto i = getKeyIndex(itr->address);
if (i < 0)
continue;
auto tm = itr->time - itr->time % 60;
auto foundLog = logs.find(tm);
if (foundLog != logs.end())
{
foundLog->second.temperature[i] = itr->temperature;
foundLog->second.humidity[i] = itr->humidity;
continue;
}
logData g;
g.time = tm;
for (std::uint_fast8_t j = 0; j < +LINE_COUNT; j++)
{
g.temperature[j] = FLT_MAX;
g.humidity[j] = FLT_MAX;
}
g.temperature[i] = itr->temperature;
g.humidity[i] = itr->humidity;
logs.insert(std::make_pair(tm, g));
if (logs.size() > maxLogCount)
logs.erase(logs.begin());
}
}
std::vector<std::string> split(const std::string &str, char sep)
{
std::vector<std::string> v;
auto first = str.begin();
while (first != str.end())
{
auto last = first;
while (last != str.end() && *last != sep)
++last;
v.push_back(std::string(first, last));
if (last != str.end())
++last;
first = last;
}
return v;
}
void clear()
{
logs.clear();
keys.clear();
}
};
namespace ScrollGraphPanel
{
// When measurement data is submitted (push), the graph is displayed (showDisp).
// The scale of the graph is adjusted according to the measurement time (if the data is submitted at high speed, it will look like scrolling).
// drawHeader drawFooter is the display part other than the graph. DrawFooter is a display section other than the graph (assume you can change it according to your use)
constexpr std::uint_fast8_t DIVIDE_COUNT = 5;
constexpr std::uint_fast8_t TEMPERATURE_MAX_DEFALT = 50;
constexpr std::int32_t colors[] = {TFT_RED, TFT_GREEN, TFT_BLUE, TFT_CYAN, TFT_MAGENTA, TFT_YELLOW};
constexpr std::int32_t fontColors[] = {TFT_WHITE, TFT_BLACK, TFT_WHITE, TFT_BLACK, TFT_WHITE, TFT_BLACK};
constexpr std::uint16_t headerHeight = 0;
constexpr std::uint16_t graphLeft = 20; // Need 20px for y-axis label display
constexpr std::uint16_t graphTop = headerHeight + 1;
constexpr std::int32_t graphWidth = GRAPH_WIDTH; // (1px=1min)
constexpr std::int32_t graphHeight = 160;
constexpr std::int32_t scaleStepW = graphWidth / DIVIDE_COUNT;
constexpr std::int32_t scaleStepH = graphHeight / DIVIDE_COUNT;
constexpr std::uint8_t labelOffset = 6;
struct point
{
std::int16_t x;
std::int16_t y;
};
std::vector<point> values[LINE_COUNT];
void drawLineW(LGFX_Sprite *sp, std::int16_t y, std::int16_t yOff, std::int32_t color)
{
std::int16_t yPos = y - yOff;
if (yPos <= 0 || yPos > spriteHeight)
return;
for (std::int16_t x = graphLeft; x < graphLeft + graphWidth; x += 3)
{
sp->drawPixel(x, yPos, color);
}
}
void drawLineH(LGFX_Sprite *sp, std::int16_t x, std::int16_t yOff, std::int32_t color)
{
std::int16_t finishY = graphTop + graphHeight - yOff;
for (std::int16_t yPos = graphTop - yOff; yPos < finishY; yPos += 3)
{
if (yPos >= 0)
sp->drawPixel(x, yPos, color);
}
}
void darwScale(LGFX_Sprite *sp, std::int16_t yOff, time_t lastTime, std::int16_t dataMax, std::int16_t dataStep)
{
sp->setTextColor(TFT_WHITE);
sp->setFont(&fonts::Font0);
std::int32_t timeOff = lastTime / 60 % 60;
std::int16_t yPos = graphTop + graphHeight - yOff + labelOffset;
for (std::uint_fast8_t i = 0; i <= DIVIDE_COUNT; i++)
{
// yAxis
drawLineW(sp, graphTop + (scaleStepH * i), yOff, TFT_DARKGREEN);
if (dataMax < 1000 && dataMax > -1000)
{
sp->setCursor(0, graphTop + (scaleStepH * i) - yOff);
std::int16_t labelY = dataMax - (dataStep * i);
sp->printf("%2d", labelY);
}
// xAxis
std::int16_t x = graphLeft + graphWidth - timeOff - (scaleStepW * i);
if (x >= graphLeft && x <= graphLeft + graphWidth - labelOffset)
{
drawLineH(sp, x, yOff, TFT_DARKGREEN);
std::int32_t labelTime = lastTime - (3600 * i);
std::int32_t hour = (labelTime / 3600 + 9) % 24;
sp->setCursor(x - labelOffset, yPos);
sp->printf("%02d", hour);
}
}
}
void drawBody(LGFX_Sprite *sp, std::int16_t yOff, time_t lastTime, std::map<time_t, Logger::logData> *logs)
{
sp->setFont(&fonts::Font4);
std::int16_t w = 53; // graphwidth / 6;
std::int16_t h = 26; // labelHeight
std::int16_t y = 220 - h - yOff;
auto log = logs->rbegin()->second;
for (std::uint_fast8_t j = 0; j < LINE_COUNT; j++)
{
sp->fillRect(w * j, y, w, h, colors[j]);
auto f = log.temperature[j];
if (f != FLT_MAX)
{
sp->setTextColor(fontColors[j]);
sp->setCursor(w * j + 2, y + 2);
sp->printf("%2.1f", f);
}
}
std::int32_t lastTimeH = (lastTime / 3600 + 9) % 24;
std::int32_t lastTimeM = lastTime / 60 % 60;
sp->setFont(&fonts::Font2);
sp->setTextColor(TFT_WHITE);
sp->setCursor(0, 222 - yOff);
sp->printf("%dfps(%3d/%3d) log:%3d freeheap:%4dk\n", fps, loopCounter, totalDataSize, logs->size(), esp_get_free_heap_size() / 1024);
sp->fillRect(265, 220 - yOff, w, 20, TFT_LIGHTGREY);
sp->setTextColor(TFT_BLACK);
sp->setCursor(267, 222 - yOff);
sp->printf(" %02d:%02d ", lastTimeH, lastTimeM);
}
void darwGraph(LGFX_Sprite *sp, std::int16_t yOff, std::int32_t logSize, std::vector<point> *values)
{
for (std::uint_fast8_t j = 0; j < LINE_COUNT; j++)
{
sp->setColor(colors[j]);
for (auto itr = values[j].begin(); itr != values[j].end(); ++itr)
{
sp->writePixel(itr->x, itr->y - yOff);
}
}
}
float getValidSample(std::map<time_t, Logger::logData> *logs)
{
for (auto itr = logs->begin(); itr != logs->end(); ++itr)
{
for (std::uint_fast8_t j = 0; j < LINE_COUNT; j++)
{
if (itr->second.temperature[j] != FLT_MAX)
return itr->second.temperature[j];
}
}
return TEMPERATURE_MAX_DEFALT;
}
GraphUtil::axisParam getAxisRange(std::map<time_t, Logger::logData> *logs)
{
auto dataMin = getValidSample(logs);
auto dataMax = dataMin;
for (auto itr = logs->begin(); itr != logs->end(); ++itr)
{
for (std::uint_fast8_t j = 0; j < LINE_COUNT; j++)
{
auto t = itr->second.temperature[j];
if (t != FLT_MAX && t < dataMin)
dataMin = t;
if (t != FLT_MAX && t > dataMax)
dataMax = t;
}
}
return GraphUtil::getAxisRange(dataMin, dataMax, DIVIDE_COUNT);
}
point calcPoint(float max, float range, std::int32_t diffMin, float data)
{
point p;
if (data == FLT_MAX)
{
p.x = -1;
p.y = 0;
return p;
}
p.x = (std::int16_t)(graphLeft + graphWidth - diffMin);
p.y = (std::int16_t)(graphHeight * ((max - data) / range) + graphTop);
if (p.x < graphLeft || p.x > graphLeft + graphWidth || p.y < graphTop || p.y > graphTop + graphHeight)
p.x = -1;
return p;
}
void showDisp(std::map<time_t, Logger::logData> *logs)
{
// Display a graph of the last 300 minutes (graphWidth) of data from the time of the last data submitted
// Convert the logs measurements to x,y coordinates and draw the points
// x-axis displays the range of -300 minutes (=graphWidth) from the last measurement time (1px=1min)
// y-axis is fixed at 5 divisions. Adjust it to fit the range of measured values (upper and lower limits) (axisParam)
int logSize = logs->size();
if (logSize < 1)
return;
// calc x,y
auto paramY = getAxisRange(logs);
auto lastTime = logs->rbegin()->second.time;
for (auto itr = logs->begin(); itr != logs->end(); ++itr)
{
auto log = itr->second;
std::int32_t diffMin = (std::int32_t)((lastTime - log.time) / 60);
if (diffMin > 0 && diffMin < graphWidth)
{
for (std::uint_fast8_t j = 0; j < LINE_COUNT; j++)
{
auto p = calcPoint(paramY.max, paramY.range, diffMin, log.temperature[j]);
if (p.x >= graphLeft)
values[j].push_back(p);
}
}
}
// draw
lcd.startWrite();
for (std::uint16_t yPos = 0; yPos < lcdHeight; yPos += spriteHeight)
{
flip = flip ? 0 : 1;
sprites[flip].clear();
darwScale(&sprites[flip], yPos, lastTime, (std::int16_t)paramY.max, (std::int16_t)paramY.step);
darwGraph(&sprites[flip], yPos, logSize, values);
drawBody(&sprites[flip], yPos, lastTime, logs);
// std::int32_t len = spriteHeight * lcdWidth;
// if (yPos + spriteHeight > lcdHeight)
// {
// len = (lcdHeight - yPos) * lcdWidth;
// }
//lcd.pushPixelsDMA(sprites[flip].getBuffer(), len);
sprites[flip].pushSprite(0, yPos);
}
lcd.endWrite();
for (std::uint_fast8_t j = 0; j < LINE_COUNT; j++)
values[j].clear();
}
void clear()
{
for (std::uint_fast8_t j = 0; j < LINE_COUNT; j++)
values[j].clear();
}
};
void initSprite()
{
std::uint8_t div = 10; // Divide by 10 to save memory.
for (;;)
{
spriteHeight = (lcdHeight + div - 1) / div;
bool fail = false;
for (std::uint8_t i = 0; !fail && i < 2; ++i)
{
sprites[i].setColorDepth(lcd.getColorDepth());
sprites[i].setFont(&fonts::Font2);
fail = !sprites[i].createSprite(lcdWidth, spriteHeight);
}
if (!fail)
break;
for (std::uint8_t i = 0; i < 2; ++i)
{
sprites[i].deleteSprite();
}
++div;
}
}
void setup()
{
lcd.init();
lcd.setTextWrap(false);
lcd.setTextDatum(textdatum_t::middle_center);
lcdWidth = lcd.width();
lcdHeight = lcd.height();
lcd.setAddrWindow(0, 0, lcdWidth, lcdHeight);
if (lcd.width() < lcd.height())
lcd.setRotation(lcd.getRotation() ^ 1);
initSprite();
totalDataSize = sizeof(room1) / sizeof(room1[0]);
// Whitelist BLE addresses.
Logger::keys.insert(std::make_pair("AA:AA:AA:AA:AA:AA", 0));
Logger::keys.insert(std::make_pair("BB:BB:BB:BB:BB:BB", 1));
Logger::keys.insert(std::make_pair("CC:CC:CC:CC:CC:CC", 2));
Logger::keys.insert(std::make_pair("DD:DD:DD:DD:DD:DD", 3));
Logger::keys.insert(std::make_pair("EE:EE:EE:EE:EE:EE", 4));
Logger::keys.insert(std::make_pair("FF:FF:FF:FF:FF:FF", 5));
}
static std::vector<Logger::metricsLog> mLogs;
void loop()
{
// Create a measurement log for the demo
// Feed the measurement data from 6 locations into the graph
time_t tm = (loopCounter * 60);
Logger::metricsLog d1;
d1.time = tm;
d1.address = "AA:AA:AA:AA:AA:AA";
d1.name = "AA";
d1.temperature = room1[loopCounter];
d1.humidity = room1[loopCounter];
mLogs.push_back(d1);
Logger::metricsLog d2;
d2.time = tm + 1;
d2.address = "BB:BB:BB:BB:BB:BB";
d2.name = "BB";
d2.temperature = room2[loopCounter];
d2.humidity = room2[loopCounter];
mLogs.push_back(d2);
Logger::metricsLog d3;
d3.time = tm + 2;
d3.address = "CC:CC:CC:CC:CC:CC";
d3.name = "CC";
d3.temperature = room3[loopCounter];
d3.humidity = room3[loopCounter];
mLogs.push_back(d3);
Logger::metricsLog d4;
d4.time = tm + 3;
d4.address = "DD:DD:DD:DD:DD:DD";
d4.name = "DD";
d4.temperature = room1[loopCounter] + 2;
d4.humidity = room1[loopCounter] + 2;
mLogs.push_back(d4);
Logger::metricsLog d5;
d5.time = tm + 4;
d5.address = "EE:EE:EE:EE:EE:EE";
d5.name = "EE";
d5.temperature = room2[loopCounter] - 0.4;
d5.humidity = room2[loopCounter] - 0.4;
mLogs.push_back(d5);
Logger::metricsLog d6;
d6.time = tm + 5;
d6.time = tm;
d6.address = "FF:FF:FF:FF:FF:FF";
d6.name = "FF";
d6.temperature = room3[loopCounter] - 1.5;
d6.humidity = room3[loopCounter] - 1.5;
mLogs.push_back(d6);
// Put the measurement log into the panel
Logger::push(mLogs);
// Display a graph of the last 300 minutes (graphWidth) of data from the time of the last data submitted.
ScrollGraphPanel::showDisp(&Logger::logs);
mLogs.clear();
++frame_count;
sec = millis() / 1000;
if (psec != sec)
{
psec = sec;
fps = frame_count;
frame_count = 0;
}
loopCounter++;
if (loopCounter >= totalDataSize)
{
loopCounter = 0;
Logger::clear();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment