Last active
March 26, 2024 22:53
-
-
Save panda728/01a314b66890d8060b61efaef4b272ee to your computer and use it in GitHub Desktop.
M5Stackでスクロールグラフ(M5GFX ver.)(Core2で動作確認)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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