mirror of
https://github.com/revspace/operame
synced 2025-04-11 02:02:01 +00:00
Added option for sparkline
This commit is contained in:
parent
29d873d268
commit
cc2404fb80
150
SparkLine.h
Normal file
150
SparkLine.h
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// Adaped version of ESParklines to sidestep random() issue in FixedPointsArduino library
|
||||||
|
//
|
||||||
|
// ESParklines – I <3 Sparklines!
|
||||||
|
// Sparklines for ESP8266/ESP32/Arduino Displays
|
||||||
|
//
|
||||||
|
// Copyright (c) 2020 karl@pitrich.com
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <algorithm>
|
||||||
|
//#include <FixedPoints.h>
|
||||||
|
//#include <FixedPointsCommon.h>
|
||||||
|
|
||||||
|
template <typename T, typename std::enable_if<std::is_arithmetic<T>::value>::type* = nullptr>
|
||||||
|
class SparkLine
|
||||||
|
{
|
||||||
|
using drawLineFunction = std::function<void(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)>;
|
||||||
|
using Point = struct { uint16_t x; uint16_t y; };
|
||||||
|
using num_t = double;//float;//SFixed<15, 16>;
|
||||||
|
|
||||||
|
T* container;
|
||||||
|
const size_t capacity;
|
||||||
|
size_t elements;
|
||||||
|
drawLineFunction drawLine;
|
||||||
|
|
||||||
|
T findMin() const {
|
||||||
|
T lo = container[0];
|
||||||
|
for (size_t i = 0; i < elements; i++) {
|
||||||
|
if (container[i] < lo) lo = container[i];
|
||||||
|
}
|
||||||
|
return lo;
|
||||||
|
}
|
||||||
|
|
||||||
|
T findMax() const {
|
||||||
|
T hi = 0;
|
||||||
|
for (size_t i = 0; i < elements; i++) {
|
||||||
|
if (container[i] > hi) hi = container[i];
|
||||||
|
}
|
||||||
|
return hi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
SparkLine(size_t _size, drawLineFunction _dlf)
|
||||||
|
: container(new T[_size]),
|
||||||
|
capacity(_size),
|
||||||
|
drawLine(_dlf)
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~SparkLine()
|
||||||
|
{
|
||||||
|
delete[] container;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
memset(container, 0, capacity * sizeof(T));
|
||||||
|
elements = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(T value)
|
||||||
|
{
|
||||||
|
if (elements < capacity) {
|
||||||
|
container[elements] = value;
|
||||||
|
elements++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memmove(container, &container[1], (capacity - 1) * sizeof(T));
|
||||||
|
container[capacity - 1] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw Sparkline using passed drawLine function
|
||||||
|
*
|
||||||
|
* @param x left-most poont where to start the sparkline
|
||||||
|
* @param y bottom point of the sparkline – to match font redering
|
||||||
|
* @param maxWidth max width of the sparkline
|
||||||
|
* @param maxHeight max height of the sparkline
|
||||||
|
* @param lineWidth width of the stroke for the line to be drawn
|
||||||
|
*/
|
||||||
|
void draw(uint16_t x, uint16_t y,
|
||||||
|
uint16_t maxWidth, uint16_t maxHeight,
|
||||||
|
uint16_t lineWidth = 1) const
|
||||||
|
{
|
||||||
|
if (elements < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
T lo = this->findMin();
|
||||||
|
T hi = this->findMax();
|
||||||
|
num_t slope = 1.0f * (maxHeight - lineWidth) / (hi - lo);
|
||||||
|
|
||||||
|
Point lastPoint = { 0, 0 };
|
||||||
|
num_t segment = 0.0f;
|
||||||
|
|
||||||
|
size_t maxSegments = elements;
|
||||||
|
if (maxSegments > maxWidth) {
|
||||||
|
maxSegments = maxWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
num_t pixelPerSegment = 1.0f;
|
||||||
|
if (elements <= maxWidth) {
|
||||||
|
pixelPerSegment = 1.0f * maxWidth / (elements - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < maxSegments; i++) {
|
||||||
|
T value = container[i];
|
||||||
|
num_t scaledValue = maxHeight - ((value - lo) * slope);
|
||||||
|
scaledValue -= lineWidth / 2.0f;
|
||||||
|
scaledValue += y - maxHeight;
|
||||||
|
|
||||||
|
Point pt {
|
||||||
|
.x = static_cast<uint16_t>(x + segment),
|
||||||
|
.y = static_cast<uint16_t>(scaledValue)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (segment > 0) {
|
||||||
|
drawLine(lastPoint.x, lastPoint.y, pt.x, pt.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastPoint = pt;
|
||||||
|
segment += pixelPerSegment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
19
operame.ino
19
operame.ino
@ -9,6 +9,7 @@
|
|||||||
#include <logo.h>
|
#include <logo.h>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <operame_strings.h>
|
#include <operame_strings.h>
|
||||||
|
#include <SparkLine.h>
|
||||||
|
|
||||||
#define LANGUAGE "nl"
|
#define LANGUAGE "nl"
|
||||||
OperameLanguage::Texts T;
|
OperameLanguage::Texts T;
|
||||||
@ -41,6 +42,8 @@ bool add_units;
|
|||||||
bool wifi_enabled;
|
bool wifi_enabled;
|
||||||
bool mqtt_enabled;
|
bool mqtt_enabled;
|
||||||
int max_failures;
|
int max_failures;
|
||||||
|
bool sparkline_enable;
|
||||||
|
int sparkline_bufferlenght;
|
||||||
|
|
||||||
void retain(const String& topic, const String& message) {
|
void retain(const String& topic, const String& message) {
|
||||||
Serial.printf("%s %s\n", topic.c_str(), message.c_str());
|
Serial.printf("%s %s\n", topic.c_str(), message.c_str());
|
||||||
@ -54,6 +57,10 @@ void clear_sprite(int bg = TFT_BLACK) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SparkLine<uint16_t> display_sparkline(766, [&](const uint16_t x0, const uint16_t y0, const uint16_t x1, const uint16_t y1) {
|
||||||
|
sprite.drawLine(x0, y0, x1, y1, TFT_PURPLE);
|
||||||
|
});
|
||||||
|
|
||||||
void display_big(const String& text, int fg = TFT_WHITE, int bg = TFT_BLACK) {
|
void display_big(const String& text, int fg = TFT_WHITE, int bg = TFT_BLACK) {
|
||||||
clear_sprite(bg);
|
clear_sprite(bg);
|
||||||
sprite.setTextSize(1);
|
sprite.setTextSize(1);
|
||||||
@ -68,6 +75,10 @@ void display_big(const String& text, int fg = TFT_WHITE, int bg = TFT_BLACK) {
|
|||||||
sprite.setTextColor(fg, bg);
|
sprite.setTextColor(fg, bg);
|
||||||
sprite.drawString(text, display.width()/2, display.height()/2);
|
sprite.drawString(text, display.width()/2, display.height()/2);
|
||||||
|
|
||||||
|
if (sparkline_enable) {
|
||||||
|
display_sparkline.draw( 10, display.height()/4*3, display.width()-20, display.height()/2-5);
|
||||||
|
};
|
||||||
|
|
||||||
sprite.pushSprite(0, 0);
|
sprite.pushSprite(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,6 +131,7 @@ void ppm_demo() {
|
|||||||
delay(1000);
|
delay(1000);
|
||||||
for (int p = 400; p < 1200; p++) {
|
for (int p = 400; p < 1200; p++) {
|
||||||
display_ppm(p);
|
display_ppm(p);
|
||||||
|
if (sparkline_enable ) display_sparkline.add(p);
|
||||||
if (button(pin_demobutton)) {
|
if (button(pin_demobutton)) {
|
||||||
display_logo();
|
display_logo();
|
||||||
delay(500);
|
delay(500);
|
||||||
@ -128,6 +140,7 @@ void ppm_demo() {
|
|||||||
delay(30);
|
delay(30);
|
||||||
}
|
}
|
||||||
display_logo();
|
display_logo();
|
||||||
|
if (sparkline_enable ) display_sparkline.reset();
|
||||||
delay(5000);
|
delay(5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,6 +350,10 @@ void setup() {
|
|||||||
mqtt_template = WiFiSettings.string("operame_mqtt_template", "{} PPM", T.config_mqtt_template);
|
mqtt_template = WiFiSettings.string("operame_mqtt_template", "{} PPM", T.config_mqtt_template);
|
||||||
WiFiSettings.info(T.config_template_info);
|
WiFiSettings.info(T.config_template_info);
|
||||||
|
|
||||||
|
WiFiSettings.heading("sparkline");
|
||||||
|
sparkline_enable = WiFiSettings.checkbox("operame_sparkline", false, T.config_sparkline);
|
||||||
|
sparkline_bufferlenght = WiFiSettings.integer("operame_sparkline_buffer", 0, 16383, 512, T.config_sparkline_buffer); ;
|
||||||
|
|
||||||
WiFiSettings.onConnect = [] {
|
WiFiSettings.onConnect = [] {
|
||||||
display_big(T.connecting, TFT_BLUE);
|
display_big(T.connecting, TFT_BLUE);
|
||||||
check_portalbutton();
|
check_portalbutton();
|
||||||
@ -378,6 +395,7 @@ void setup() {
|
|||||||
if (mqtt_enabled) mqtt.begin(server.c_str(), port, wificlient);
|
if (mqtt_enabled) mqtt.begin(server.c_str(), port, wificlient);
|
||||||
|
|
||||||
if (ota_enabled) setup_ota();
|
if (ota_enabled) setup_ota();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define every(t) for (static unsigned long _lasttime; (unsigned long)((unsigned long)millis() - _lasttime) >= (t); _lasttime = millis())
|
#define every(t) for (static unsigned long _lasttime; (unsigned long)((unsigned long)millis() - _lasttime) >= (t); _lasttime = millis())
|
||||||
@ -388,6 +406,7 @@ void loop() {
|
|||||||
every(5000) {
|
every(5000) {
|
||||||
co2 = get_co2();
|
co2 = get_co2();
|
||||||
Serial.println(co2);
|
Serial.println(co2);
|
||||||
|
if (sparkline_enable ) display_sparkline.add(co2);
|
||||||
}
|
}
|
||||||
|
|
||||||
every(50) {
|
every(50) {
|
||||||
|
@ -26,6 +26,8 @@ struct Texts {
|
|||||||
*config_mqtt_interval,
|
*config_mqtt_interval,
|
||||||
*config_mqtt_template,
|
*config_mqtt_template,
|
||||||
*config_template_info,
|
*config_template_info,
|
||||||
|
*config_sparkline,
|
||||||
|
*config_sparkline_buffer,
|
||||||
*connecting,
|
*connecting,
|
||||||
*wait
|
*wait
|
||||||
;
|
;
|
||||||
@ -71,6 +73,8 @@ bool select(Texts& T, String language) {
|
|||||||
T.config_mqtt_interval = "Publication interval [s]";
|
T.config_mqtt_interval = "Publication interval [s]";
|
||||||
T.config_mqtt_template = "Message template";
|
T.config_mqtt_template = "Message template";
|
||||||
T.config_template_info = "The {} in the template is replaced by the measurement value.";
|
T.config_template_info = "The {} in the template is replaced by the measurement value.";
|
||||||
|
T.config_sparkline = "Show sparkline graph";
|
||||||
|
T.config_sparkline_buffer= "sparkline buffer length";
|
||||||
T.connecting = "Connecting to WiFi...";
|
T.connecting = "Connecting to WiFi...";
|
||||||
T.portal_instructions = {
|
T.portal_instructions = {
|
||||||
{
|
{
|
||||||
@ -126,6 +130,8 @@ bool select(Texts& T, String language) {
|
|||||||
T.config_mqtt_interval = "Publicatie-interval [s]";
|
T.config_mqtt_interval = "Publicatie-interval [s]";
|
||||||
T.config_mqtt_template = "Berichtsjabloon";
|
T.config_mqtt_template = "Berichtsjabloon";
|
||||||
T.config_template_info = "De {} in het sjabloon wordt vervangen door de gemeten waarde.";
|
T.config_template_info = "De {} in het sjabloon wordt vervangen door de gemeten waarde.";
|
||||||
|
T.config_sparkline = "Laat grafiek zien";
|
||||||
|
T.config_sparkline_buffer= "grafiek buffer lengte";
|
||||||
T.connecting = "Verbinden met WiFi...";
|
T.connecting = "Verbinden met WiFi...";
|
||||||
T.portal_instructions = {
|
T.portal_instructions = {
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user