Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 591562e

Browse files
committedNov 29, 2024··
ESP-DASH v5
1 parent edd847e commit 591562e

21 files changed

+2031
-1034
lines changed
 

‎.clang-format

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Language: Cpp
2+
BasedOnStyle: LLVM
3+
4+
AccessModifierOffset: -2
5+
AlignConsecutiveMacros: true
6+
AllowAllArgumentsOnNextLine: false
7+
AllowAllParametersOfDeclarationOnNextLine: false
8+
AllowShortIfStatementsOnASingleLine: false
9+
AllowShortLambdasOnASingleLine: Inline
10+
BinPackArguments: false
11+
ColumnLimit: 0
12+
ContinuationIndentWidth: 2
13+
FixNamespaceComments: false
14+
IndentAccessModifiers: true
15+
IndentCaseLabels: true
16+
IndentPPDirectives: BeforeHash
17+
IndentWidth: 2
18+
NamespaceIndentation: All
19+
PointerAlignment: Left
20+
ReferenceAlignment: Left
21+
TabWidth: 2
22+
UseTab: Never

‎.github/workflows/ci.yml

+4
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ jobs:
7878
- name: Build Benchmark
7979
run: arduino-cli compile --library . --warnings none -b ${{ matrix.board }} "examples/Benchmark/Benchmark.ino"
8080

81+
- name: Build Benchmark5
82+
run: arduino-cli compile --library . --warnings none -b ${{ matrix.board }} "examples/Benchmark5/Benchmark5.ino"
83+
8184
- name: Build Chart
8285
run: arduino-cli compile --library . --warnings none -b ${{ matrix.board }} "examples/Chart/Chart.ino"
8386

@@ -154,6 +157,7 @@ jobs:
154157
- run: PLATFORMIO_SRC_DIR=examples/AccessPoint PIO_BOARD=${{ matrix.board }} pio run -e ${{ matrix.env }}
155158
- run: PLATFORMIO_SRC_DIR=examples/Basic PIO_BOARD=${{ matrix.board }} pio run -e ${{ matrix.env }}
156159
- run: PLATFORMIO_SRC_DIR=examples/Benchmark PIO_BOARD=${{ matrix.board }} pio run -e ${{ matrix.env }}
160+
- run: PLATFORMIO_SRC_DIR=examples/Benchmark5 PIO_BOARD=${{ matrix.board }} pio run -e ${{ matrix.env }}
157161
- run: PLATFORMIO_SRC_DIR=examples/Chart PIO_BOARD=${{ matrix.board }} pio run -e ${{ matrix.env }}
158162
- run: PLATFORMIO_SRC_DIR=examples/Dynamic PIO_BOARD=${{ matrix.board }} pio run -e ${{ matrix.env }}
159163
- run: PLATFORMIO_SRC_DIR=examples/Interactive PIO_BOARD=${{ matrix.board }} pio run -e ${{ matrix.env }}

‎examples/Benchmark/Benchmark.ino

+19-16
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ ESPDash dashboard(&server);
4242
Card generic(&dashboard, GENERIC_CARD, "Generic");
4343
Card temp(&dashboard, TEMPERATURE_CARD, "Temperature", "°C");
4444
Card hum(&dashboard, HUMIDITY_CARD, "Humidity", "%");
45-
Card status1(&dashboard, STATUS_CARD, "Status 1", "success");
46-
Card status2(&dashboard, STATUS_CARD, "Status 2", "warning");
47-
Card status3(&dashboard, STATUS_CARD, "Status 3", "danger");
48-
Card status4(&dashboard, STATUS_CARD, "Status 4", "idle");
45+
Card status1(&dashboard, STATUS_CARD, "Status 1", DASH_STATUS_SUCCESS);
46+
Card status2(&dashboard, STATUS_CARD, "Status 2", DASH_STATUS_WARNING);
47+
Card status3(&dashboard, STATUS_CARD, "Status 3", DASH_STATUS_DANGER);
48+
Card status4(&dashboard, STATUS_CARD, "Status 4", DASH_STATUS_IDLE);
4949
Card progress(&dashboard, PROGRESS_CARD, "Progress", "", 0, 100);
5050
Card button(&dashboard, BUTTON_CARD, "Test Button");
5151
Card slider(&dashboard, SLIDER_CARD, "Test Slider", "", 0, 255);
@@ -61,14 +61,17 @@ void setup() {
6161
Serial.begin(115200);
6262

6363
/* Connect WiFi */
64-
WiFi.mode(WIFI_STA);
65-
WiFi.begin(ssid, password);
66-
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
67-
Serial.printf("WiFi Failed!\n");
68-
return;
69-
}
70-
Serial.print("IP Address: ");
71-
Serial.println(WiFi.localIP());
64+
// WiFi.mode(WIFI_STA);
65+
// WiFi.begin(ssid, password);
66+
// if (WiFi.waitForConnectResult() != WL_CONNECTED) {
67+
// Serial.printf("WiFi Failed!\n");
68+
// return;
69+
// }
70+
// Serial.print("IP Address: ");
71+
// Serial.println(WiFi.localIP());
72+
73+
WiFi.mode(WIFI_AP);
74+
WiFi.softAP("esp-captive");
7275

7376
bar.updateX(XAxis, 7);
7477

@@ -107,10 +110,10 @@ void loop() {
107110
generic.update((int)random(0, 100));
108111
temp.update((int)random(0, 100));
109112
hum.update((int)random(0, 100));
110-
status1.update(DASH_STATUS_SUCCESS);
111-
status2.update(DASH_STATUS_WARNING);
112-
status3.update(DASH_STATUS_DANGER);
113-
status4.update(DASH_STATUS_IDLE);
113+
status1.update("message 1", DASH_STATUS_SUCCESS);
114+
status2.update("message 2", DASH_STATUS_WARNING);
115+
status3.update("message 3", DASH_STATUS_DANGER);
116+
status4.update("message 4", DASH_STATUS_IDLE);
114117
progress.update((int)random(0, 100));
115118

116119
dashboard.sendUpdates();

‎examples/Benchmark5/Benchmark5.ino

+200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*
2+
-----------------------------
3+
ESPDASH Pro - Benchmark Example
4+
-----------------------------
5+
Use this benchmark example to test if ESP-DASH Pro is working properly on your platform.
6+
7+
Github: https://github.com/ayushsharma82/ESP-DASH
8+
WiKi: https://docs.espdash.pro
9+
10+
Works with both ESP8266 & ESP32
11+
-------------------------------
12+
*/
13+
14+
#include <Arduino.h>
15+
#if defined(ESP8266)
16+
/* ESP8266 Dependencies */
17+
#include <ESP8266WiFi.h>
18+
#include <ESPAsyncTCP.h>
19+
#include <ESPAsyncWebServer.h>
20+
#elif defined(ESP32)
21+
/* ESP32 Dependencies */
22+
#include <AsyncTCP.h>
23+
#include <ESPAsyncWebServer.h>
24+
#include <WiFi.h>
25+
#endif
26+
27+
#include <ESPDash.h>
28+
29+
/* Your WiFi Credentials */
30+
const char* ssid = ""; // SSID
31+
const char* password = ""; // Password
32+
33+
/* Start Webserver */
34+
AsyncWebServer server(80);
35+
36+
/* Attach ESP-DASH to AsyncWebServer */
37+
ESPDash dashboard(server, true);
38+
39+
// Cards
40+
dash::FeedbackCard feedback(dashboard, "Status", dash::Status::SUCCESS);
41+
dash::GenericCard genericString(dashboard, "Generic String");
42+
dash::GenericCard<float> genericFloat(dashboard, "Generic Float");
43+
dash::GenericCard<int> genericInt(dashboard, "Generic Int");
44+
dash::HumidityCard<float, 3> hum(dashboard, "Humidity"); // set decimal precision is 3
45+
dash::ProgressCard<float, 4> progressFloat(dashboard, "Progress Float", 0, 1, "kWh");
46+
dash::ProgressCard progressInt(dashboard, "Progress Int", 0, 100, "%");
47+
dash::SliderCard<float, 4> sliderFloatP4(dashboard, "Float Slider (4)", 0, 1, 0.0001f);
48+
dash::SliderCard<float> sliderFloatP2(dashboard, "Float Slider (2)", 0, 1, 0.01f);
49+
dash::SliderCard sliderInt(dashboard, "Int Slider", 0, 255, 1, "bits");
50+
dash::SliderCard<uint32_t> updateDelay(dashboard, "Update Delay", 1000, 20000, 1000, "ms");
51+
dash::SwitchCard button(dashboard, "Button");
52+
dash::TemperatureCard temp(dashboard, "Temperature"); // default precision is 2
53+
54+
// Charts
55+
dash::BarChart<const char*, int> bar(dashboard, "Power Usage (kWh)");
56+
57+
// Custom Statistics
58+
dash::StatisticValue stat1(dashboard, "Statistic 1");
59+
dash::StatisticValue<float, 4> stat2(dashboard, "Statistic 2");
60+
dash::StatisticProvider<uint32_t> statProvider(dashboard, "Statistic Provider");
61+
62+
uint8_t test_status = 0;
63+
64+
/**
65+
* Note how we are keeping all the chart data in global scope.
66+
*/
67+
// Bar Chart Data
68+
const char* BarXAxis[] = {"1/4/22", "2/4/22", "3/4/22", "4/4/22", "5/4/22", "6/4/22", "7/4/22", "8/4/22", "9/4/22", "10/4/22", "11/4/22", "12/4/22", "13/4/22", "14/4/22", "15/4/22", "16/4/22", "17/4/22", "18/4/22", "19/4/22", "20/4/22", "21/4/22", "22/4/22", "23/4/22", "24/4/22", "25/4/22", "26/4/22", "27/4/22", "28/4/22", "29/4/22", "30/4/22"};
69+
int BarYAxis[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
70+
71+
unsigned long last_update_millis = 0;
72+
uint32_t update_delay = 2000;
73+
74+
void setup() {
75+
Serial.begin(115200);
76+
Serial.println();
77+
/* Connect WiFi */
78+
79+
// WiFi.persistent(false);
80+
// WiFi.mode(WIFI_STA);
81+
// WiFi.begin(ssid, password);
82+
// while (WiFi.status() != WL_CONNECTED) {
83+
// delay(500);
84+
// Serial.print(".");
85+
// }
86+
// Serial.print("IP Address: ");
87+
// Serial.println(WiFi.localIP());
88+
89+
WiFi.mode(WIFI_AP);
90+
WiFi.softAP("esp-captive");
91+
92+
/* Attach Button Callback */
93+
button.onChange([&](bool state) {
94+
/* Print our new button value received from dashboard */
95+
Serial.println(String("Button Triggered: ") + (state ? "true" : "false"));
96+
/* Make sure we update our button's value and send update to dashboard */
97+
button.setValue(state);
98+
dashboard.refresh(button);
99+
});
100+
101+
// Set Slider Index
102+
sliderInt.setIndex(1);
103+
104+
/* Attach Slider Callback */
105+
sliderInt.onChange([&](int value) {
106+
/* Print our new slider value received from dashboard */
107+
Serial.println("Slider Triggered: " + String(value));
108+
/* Make sure we update our slider's value and send update to dashboard */
109+
sliderInt.setValue(value);
110+
dashboard.refresh(sliderInt);
111+
});
112+
113+
sliderFloatP2.setIndex(2);
114+
sliderFloatP2.onChange([&](float value) {
115+
Serial.println("Slider Float P2 Triggered: " + String(value));
116+
sliderFloatP2.setValue(value);
117+
dashboard.refresh(sliderFloatP2);
118+
});
119+
120+
sliderFloatP4.setIndex(3);
121+
sliderFloatP4.onChange([&](float value) {
122+
Serial.println("Slider Float P4 Triggered: " + String(value, 4));
123+
sliderFloatP4.setValue(value);
124+
dashboard.refresh(sliderFloatP4);
125+
});
126+
127+
updateDelay.setValue(update_delay);
128+
updateDelay.onChange([&](uint32_t value) {
129+
update_delay = value;
130+
updateDelay.setValue(value);
131+
dashboard.refresh(updateDelay);
132+
});
133+
134+
stat1.setValue("Value 1");
135+
stat2.setValue(10.0 / 3.0);
136+
statProvider.setProvider([]() { return millis(); });
137+
138+
bar.setX(BarXAxis, 30);
139+
140+
genericFloat.setValue(10.0 / 3.0); // default rounding is 2
141+
genericString.setValue("Hello World!");
142+
143+
/* Start AsyncWebServer */
144+
server.begin();
145+
146+
server.onNotFound([](AsyncWebServerRequest* request) {
147+
request->send(404);
148+
});
149+
}
150+
151+
void loop() {
152+
// Update Everything every 2 seconds using millis if connected to WiFi
153+
if (millis() - last_update_millis > update_delay && dashboard.hasClient()) {
154+
last_update_millis = millis();
155+
156+
// Randomize Bar Chart YAxis Values ( for demonstration purposes only )
157+
for (int i = 0; i < 30; i++) {
158+
BarYAxis[i] = (int)random(0, 200);
159+
}
160+
161+
/* Update Chart Y Axis (yaxis_array, array_size) */
162+
bar.setY(BarYAxis, 30);
163+
164+
// Update all cards with random values
165+
genericInt.setSymbol(random(0, 2) ? "unit1" : "unit2");
166+
genericInt.setValue((int)random(0, 100));
167+
temp.setValue(random(0, 100) / 3.0);
168+
hum.setValue(random(0, 100) / 3.0);
169+
170+
progressInt.setValue(random(0, 200)); // if more than max, clamped to max
171+
progressFloat.setValue(random(0, 1000) / 2000.0); // if more than max, clamped to max
172+
173+
sliderInt.setValue(random(0, 200)); // clamped at 255 by max
174+
sliderFloatP2.setValue(random(0, 100) / 333.0);
175+
sliderFloatP4.setValue(random(0, 100) / 333.0);
176+
177+
// Loop through statuses
178+
if (test_status == 0) {
179+
feedback.setFeedback("Success Msg!", dash::Status::SUCCESS);
180+
test_status = 1;
181+
} else if (test_status == 1) {
182+
feedback.setFeedback("Warning Msg!", dash::Status::WARNING);
183+
test_status = 2;
184+
} else if (test_status == 2) {
185+
feedback.setFeedback("Danger Msg!", dash::Status::DANGER);
186+
test_status = 3;
187+
} else if (test_status == 3) {
188+
feedback.setFeedback("Idle Msg!", dash::Status::IDLE);
189+
test_status = 0;
190+
}
191+
192+
if (random(0, 2))
193+
button.toggle();
194+
195+
// Get Free Heap
196+
Serial.println("Free Heap (Before Update): " + String(ESP.getFreeHeap()));
197+
dashboard.sendUpdates();
198+
Serial.println("Free Heap (After Update): " + String(ESP.getFreeHeap()));
199+
}
200+
}

‎platformio.ini

+9-3
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,25 @@
22
lib_dir = .
33
; src_dir = examples/AccessPoint
44
; src_dir = examples/Basic
5-
src_dir = examples/Benchmark
5+
; src_dir = examples/Benchmark
6+
src_dir = examples/Benchmark5
67
; src_dir = examples/Chart
78
; src_dir = examples/Dynamic
89
; src_dir = examples/Interactive
910

1011
[env]
1112
framework = arduino
1213
build_flags =
14+
-std=c++17
15+
-std=gnu++17
1316
-Wall -Wextra
1417
-D CONFIG_ARDUHAL_LOG_COLORS
1518
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
1619
; -D DASH_USE_LEGACY_CHART_STORAGE=1
1720
; -D DASH_USE_STL_STRING=1
21+
; -D DASH_DEBUG
22+
build_unflags =
23+
-std=gnu++11
1824
lib_deps =
1925
bblanchon/ArduinoJson@^7.2.1
2026
mathieucarbou/ESPAsyncWebServer@^3.3.23
@@ -28,8 +34,8 @@ board = esp32-s3-devkitc-1
2834

2935
[env:arduino-3]
3036
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc1/platform-espressif32.zip
31-
board = esp32-s3-devkitc-1
32-
; board = esp32dev
37+
; board = esp32-s3-devkitc-1
38+
board = esp32dev
3339

3440
[env:esp8266]
3541
platform = espressif8266

‎src/Card.cpp

-185
This file was deleted.

‎src/Card.h

+30-60
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,29 @@
11
#ifndef __CARD_H
22
#define __CARD_H
33

4+
#include "dash/DashCards.h"
45

5-
#include <functional>
6-
#include "Arduino.h"
6+
#define BUTTON_CARD dash::Component::Type::CARD_BUTTON
7+
#define GENERIC_CARD dash::Component::Type::CARD_GENERIC
8+
#define HUMIDITY_CARD dash::Component::Type::CARD_HUMIDITY
9+
#define PROGRESS_CARD dash::Component::Type::CARD_PROGRESS
10+
#define SLIDER_CARD dash::Component::Type::CARD_SLIDER
11+
#define STATUS_CARD dash::Component::Type::CARD_STATUS
12+
#define TEMPERATURE_CARD dash::Component::Type::CARD_TEMPERATURE
713

8-
#include "ESPDash.h"
9-
#include "ArduinoJson.h"
10-
11-
#ifdef DASH_USE_STL_STRING
12-
#include <string>
13-
namespace dash {
14-
using string = std::string;
15-
}
16-
#else
17-
namespace dash {
18-
using string = String;
19-
}
20-
#endif
21-
22-
struct CardNames {
23-
int value;
24-
const char* type;
25-
};
26-
27-
// functions defaults to zero (number card)
28-
enum {
29-
GENERIC_CARD,
30-
TEMPERATURE_CARD,
31-
HUMIDITY_CARD,
32-
STATUS_CARD,
33-
SLIDER_CARD,
34-
BUTTON_CARD,
35-
PROGRESS_CARD
36-
};
37-
38-
// Forward Declaration
39-
class ESPDash;
14+
#define DASH_STATUS_IDLE "i"
15+
#define DASH_STATUS_SUCCESS "s"
16+
#define DASH_STATUS_WARNING "w"
17+
#define DASH_STATUS_DANGER "d"
4018

4119
// Card Class
42-
class Card {
20+
class [[deprecated("This class is deprecated. Use a dash::Card sub-class instead.")]] Card : public dash::Widget {
4321
private:
44-
ESPDash *_dashboard;
22+
ESPDash* _dashboard = nullptr;
4523

46-
uint32_t _id;
47-
const char* _name;
48-
int _type;
49-
bool _changed;
50-
enum { INTEGER, FLOAT, STRING } _value_type;
24+
enum { INTEGER,
25+
FLOAT,
26+
STRING } _value_type;
5127
union alignas(4) {
5228
float _value_f;
5329
int _value_i;
@@ -66,30 +42,24 @@ class Card {
6642
int _value_step;
6743
};
6844
dash::string _symbol;
45+
6946
std::function<void(int value)> _callback = nullptr;
7047
std::function<void(float value)> _callback_f = nullptr;
7148

7249
public:
73-
Card(ESPDash *dashboard, const int type, const char* name, const char* symbol = "", const int min = 0, const int max = 0, const int step = 1);
74-
Card(ESPDash *dashboard, const int type, const char* name, const char* symbol, const float min, const float max, const float step);
75-
void attachCallback(std::function<void(int)> cb);
76-
void attachCallbackF(std::function<void(float)> cb);
77-
void update(int value);
78-
void update(int value, const char* symbol);
79-
void update(bool value);
80-
void update(bool value, const char* symbol);
81-
void update(float value);
82-
void update(float value, const char* symbol);
83-
void update(const char* value);
84-
void update(const char* value, const char* symbol);
85-
void update(const dash::string &value);
86-
void update(const dash::string &value, const char* symbol);
87-
void update(dash::string &&value);
88-
void update(dash::string &&value, const char* symbol);
50+
Card(ESPDash* dashboard, const dash::Component::Type type, const char* name, const char* symbol = "", const int min = 0, const int max = 0, const int step = 1);
51+
Card(ESPDash* dashboard, const dash::Component::Type type, const char* name, const char* symbol, const float min, const float max, const float step);
52+
void attachCallback(std::function<void(int)> cb) { _callback = cb; }
53+
void attachCallbackF(std::function<void(float)> cb) { _callback_f = cb; }
54+
void update(int value, const char* symbol = nullptr);
55+
void update(bool value, const char* symbol = nullptr);
56+
void update(float value, const char* symbol = nullptr);
57+
void update(const char* value, const char* symbol = nullptr);
58+
void update(const dash::string& value, const char* symbol = nullptr) { update(value.c_str(), symbol); }
59+
void update(dash::string&& value, const char* symbol = nullptr);
60+
virtual void toJson(const JsonObject& json, bool onlyChanges) const override;
61+
virtual void onEvent(const JsonObject& json) const override;
8962
~Card();
90-
91-
friend class ESPDash;
9263
};
9364

94-
9565
#endif

‎src/Chart.cpp

-144
This file was deleted.

‎src/Chart.h

+36-66
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
#ifndef __CHART_H
22
#define __CHART_H
33

4-
#include <functional>
5-
#include "Arduino.h"
6-
7-
#include "ESPDash.h"
8-
#include "ArduinoJson.h"
4+
#include "dash/DashWidget.h"
95

106
#ifndef DASH_USE_LEGACY_CHART_STORAGE
117
#define DASH_USE_LEGACY_CHART_STORAGE 0
@@ -15,83 +11,57 @@
1511
#include <vector>
1612
#endif
1713

18-
#ifdef DASH_USE_STL_STRING
19-
#include <string>
20-
namespace dash {
21-
using string = std::string;
22-
}
23-
#else
24-
namespace dash {
25-
using string = String;
26-
}
27-
#endif
28-
29-
// Default to Line Chart
30-
enum {
31-
BAR_CHART,
32-
};
33-
34-
struct ChartNames {
35-
int value;
36-
const char* type;
37-
};
38-
39-
enum GraphAxisType { INTEGER, FLOAT, CHAR, STRING };
14+
enum GraphAxisType { INTEGER,
15+
FLOAT,
16+
CHAR,
17+
STRING };
4018

41-
// Forward Declaration
42-
class ESPDash;
19+
#define BAR_CHART dash::Component::Type::CHART_BAR
4320

4421
// Chart Class
45-
class Chart {
22+
class [[deprecated("This class is deprecated. Use a dash::Chart sub-class instead.")]] Chart : public dash::Widget {
4623
private:
47-
ESPDash *_dashboard;
48-
49-
uint32_t _id;
50-
const char *_name;
51-
int _type;
52-
bool _x_changed;
53-
bool _y_changed;
24+
ESPDash* _dashboard = nullptr;
5425
GraphAxisType _x_axis_type;
5526
GraphAxisType _y_axis_type;
5627

57-
#if DASH_USE_LEGACY_CHART_STORAGE == 1
58-
/* X-Axis */
59-
std::vector<int> _x_axis_i;
60-
std::vector<float> _x_axis_f;
61-
std::vector<dash::string> _x_axis_s;
62-
/* Y-Axis */
63-
std::vector<int> _y_axis_i;
64-
std::vector<float> _y_axis_f;
65-
66-
void emptyXAxisVectors();
67-
void emptyYAxisVectors();
68-
#else
69-
/* X-Axis */
70-
int *_x_axis_i_ptr = nullptr;
71-
float *_x_axis_f_ptr = nullptr;
72-
const char **_x_axis_char_ptr = nullptr;
73-
dash::string *_x_axis_s_ptr = nullptr;
74-
unsigned int _x_axis_ptr_size = 0;
75-
/* Y-Axis */
76-
int *_y_axis_i_ptr = nullptr;
77-
float *_y_axis_f_ptr = nullptr;
78-
unsigned int _y_axis_ptr_size = 0;
79-
80-
void clearXAxisPointers();
81-
void clearYAxisPointers();
82-
#endif
28+
#if DASH_USE_LEGACY_CHART_STORAGE == 1
29+
/* X-Axis */
30+
std::vector<int> _x_axis_i;
31+
std::vector<float> _x_axis_f;
32+
std::vector<dash::string> _x_axis_s;
33+
/* Y-Axis */
34+
std::vector<int> _y_axis_i;
35+
std::vector<float> _y_axis_f;
36+
37+
void emptyXAxisVectors();
38+
void emptyYAxisVectors();
39+
#else
40+
/* X-Axis */
41+
int* _x_axis_i_ptr = nullptr;
42+
float* _x_axis_f_ptr = nullptr;
43+
const char** _x_axis_char_ptr = nullptr;
44+
dash::string* _x_axis_s_ptr = nullptr;
45+
unsigned int _x_axis_ptr_size = 0;
46+
/* Y-Axis */
47+
int* _y_axis_i_ptr = nullptr;
48+
float* _y_axis_f_ptr = nullptr;
49+
unsigned int _y_axis_ptr_size = 0;
50+
51+
void clearXAxisPointers();
52+
void clearYAxisPointers();
53+
#endif
8354

8455
public:
85-
Chart(ESPDash *dashboard, const int type, const char* name);
56+
Chart(ESPDash* dashboard, const dash::Component::Type type, const char* name) : dash::Widget(*dashboard, name, type), _dashboard(dashboard) {}
8657
void updateX(int arr_x[], size_t x_size);
8758
void updateX(float arr_x[], size_t x_size);
8859
void updateX(dash::string arr_x[], size_t x_size);
8960
void updateX(const char* arr_x[], size_t x_size);
9061
void updateY(int arr_y[], size_t y_size);
9162
void updateY(float arr_y[], size_t y_size);
63+
virtual void toJson(const JsonObject& json, bool onlyChanges) const override;
9264
~Chart();
93-
94-
friend class ESPDash;
9565
};
9666

9767
#endif

‎src/DashDeprecations.cpp

+385
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,385 @@
1+
// This file holds all the deprecated class and method implementations
2+
3+
#include "Card.h"
4+
#include "Chart.h"
5+
#include "Statistic.h"
6+
7+
#include "ESPDash.h"
8+
9+
Statistic::~Statistic() { _dashboard->remove(*this); }
10+
11+
/*
12+
Chart
13+
*/
14+
15+
#if DASH_USE_LEGACY_CHART_STORAGE == 1
16+
void Chart::emptyXAxisVectors() {
17+
if (!_x_axis_i.empty())
18+
_x_axis_i.clear();
19+
if (!_x_axis_f.empty())
20+
_x_axis_f.clear();
21+
if (!_x_axis_s.empty())
22+
_x_axis_s.clear();
23+
}
24+
25+
void Chart::emptyYAxisVectors() {
26+
if (!_y_axis_i.empty())
27+
_y_axis_i.clear();
28+
if (!_y_axis_f.empty())
29+
_y_axis_f.clear();
30+
}
31+
#else
32+
void Chart::clearXAxisPointers() {
33+
_x_axis_i_ptr = nullptr;
34+
_x_axis_f_ptr = nullptr;
35+
_x_axis_char_ptr = nullptr;
36+
_x_axis_s_ptr = nullptr;
37+
_x_axis_ptr_size = 0;
38+
}
39+
40+
void Chart::clearYAxisPointers() {
41+
_y_axis_i_ptr = nullptr;
42+
_y_axis_f_ptr = nullptr;
43+
_y_axis_ptr_size = 0;
44+
}
45+
#endif
46+
47+
void Chart::updateX(int arr_x[], size_t x_size) {
48+
_x_axis_type = GraphAxisType::INTEGER;
49+
#if DASH_USE_LEGACY_CHART_STORAGE == 1
50+
emptyXAxisVectors();
51+
for (int i = 0; i < x_size; i++) {
52+
_x_axis_i.push_back(arr_x[i]);
53+
}
54+
#else
55+
clearXAxisPointers();
56+
_x_axis_i_ptr = arr_x;
57+
_x_axis_ptr_size = x_size;
58+
#endif
59+
setChange(Property::AXIS_X);
60+
}
61+
62+
void Chart::updateX(float arr_x[], size_t x_size) {
63+
_x_axis_type = GraphAxisType::FLOAT;
64+
#if DASH_USE_LEGACY_CHART_STORAGE == 1
65+
emptyXAxisVectors();
66+
for (int i = 0; i < x_size; i++) {
67+
_x_axis_f.push_back(arr_x[i]);
68+
}
69+
#else
70+
clearXAxisPointers();
71+
_x_axis_f_ptr = arr_x;
72+
_x_axis_ptr_size = x_size;
73+
#endif
74+
setChange(Property::AXIS_X);
75+
}
76+
77+
void Chart::updateX(dash::string arr_x[], size_t x_size) {
78+
_x_axis_type = GraphAxisType::STRING;
79+
#if DASH_USE_LEGACY_CHART_STORAGE == 1
80+
emptyXAxisVectors();
81+
for (int i = 0; i < x_size; i++) {
82+
_x_axis_s.push_back(arr_x[i].c_str());
83+
}
84+
#else
85+
clearXAxisPointers();
86+
_x_axis_s_ptr = arr_x;
87+
_x_axis_ptr_size = x_size;
88+
#endif
89+
setChange(Property::AXIS_X);
90+
}
91+
92+
void Chart::updateX(const char* arr_x[], size_t x_size) {
93+
_x_axis_type = GraphAxisType::CHAR;
94+
#if DASH_USE_LEGACY_CHART_STORAGE == 1
95+
emptyXAxisVectors();
96+
for (int i = 0; i < x_size; i++) {
97+
_x_axis_s.push_back(arr_x[i]);
98+
}
99+
#else
100+
clearXAxisPointers();
101+
_x_axis_char_ptr = arr_x;
102+
_x_axis_ptr_size = x_size;
103+
#endif
104+
setChange(Property::AXIS_X);
105+
}
106+
107+
void Chart::updateY(int arr_y[], size_t y_size) {
108+
_y_axis_type = GraphAxisType::INTEGER;
109+
#if DASH_USE_LEGACY_CHART_STORAGE == 1
110+
emptyYAxisVectors();
111+
for (int i = 0; i < y_size; i++) {
112+
_y_axis_i.push_back(arr_y[i]);
113+
}
114+
#else
115+
clearYAxisPointers();
116+
_y_axis_i_ptr = arr_y;
117+
_y_axis_ptr_size = y_size;
118+
#endif
119+
setChange(Property::AXIS_Y);
120+
}
121+
122+
void Chart::updateY(float arr_y[], size_t y_size) {
123+
_y_axis_type = GraphAxisType::FLOAT;
124+
#if DASH_USE_LEGACY_CHART_STORAGE == 1
125+
emptyYAxisVectors();
126+
for (int i = 0; i < y_size; i++) {
127+
_y_axis_f.push_back(arr_y[i]);
128+
}
129+
#else
130+
clearYAxisPointers();
131+
_y_axis_f_ptr = arr_y;
132+
_y_axis_ptr_size = y_size;
133+
#endif
134+
setChange(Property::AXIS_Y);
135+
}
136+
137+
void Chart::toJson(const JsonObject& json, bool onlyChanges) const {
138+
dash::Widget::toJson(json, onlyChanges);
139+
140+
if (!onlyChanges || hasChanged(Property::AXIS_X)) {
141+
JsonArray xAxis = json["x"].to<JsonArray>();
142+
switch (_x_axis_type) {
143+
case GraphAxisType::INTEGER:
144+
#if DASH_USE_LEGACY_CHART_STORAGE == 1
145+
for (int i = 0; i < _x_axis_i.size(); i++)
146+
xAxis.add(_x_axis_i[i]);
147+
#else
148+
if (_x_axis_i_ptr != nullptr) {
149+
for (unsigned int i = 0; i < _x_axis_ptr_size; i++)
150+
xAxis.add(_x_axis_i_ptr[i]);
151+
}
152+
#endif
153+
break;
154+
case GraphAxisType::FLOAT:
155+
#if DASH_USE_LEGACY_CHART_STORAGE == 1
156+
for (int i = 0; i < _x_axis_f.size(); i++)
157+
xAxis.add(_x_axis_f[i]);
158+
#else
159+
if (_x_axis_f_ptr != nullptr) {
160+
for (unsigned int i = 0; i < _x_axis_ptr_size; i++)
161+
xAxis.add(_x_axis_f_ptr[i]);
162+
}
163+
#endif
164+
break;
165+
case GraphAxisType::CHAR:
166+
#if DASH_USE_LEGACY_CHART_STORAGE == 1
167+
for (int i = 0; i < _x_axis_s.size(); i++)
168+
xAxis.add(_x_axis_s[i].c_str());
169+
#else
170+
if (_x_axis_char_ptr != nullptr) {
171+
for (unsigned int i = 0; i < _x_axis_ptr_size; i++)
172+
xAxis.add(_x_axis_char_ptr[i]);
173+
}
174+
#endif
175+
break;
176+
case GraphAxisType::STRING:
177+
#if DASH_USE_LEGACY_CHART_STORAGE == 1
178+
for (int i = 0; i < _x_axis_s.size(); i++)
179+
xAxis.add(_x_axis_s[i].c_str());
180+
#else
181+
if (_x_axis_s_ptr != nullptr) {
182+
for (unsigned int i = 0; i < _x_axis_ptr_size; i++)
183+
xAxis.add(_x_axis_s_ptr[i]);
184+
}
185+
#endif
186+
break;
187+
default:
188+
// blank value
189+
break;
190+
}
191+
}
192+
193+
if (!onlyChanges || hasChanged(Property::AXIS_Y)) {
194+
JsonArray yAxis = json["y"].to<JsonArray>();
195+
switch (_y_axis_type) {
196+
case GraphAxisType::INTEGER:
197+
#if DASH_USE_LEGACY_CHART_STORAGE == 1
198+
for (int i = 0; i < _y_axis_i.size(); i++)
199+
yAxis.add(_y_axis_i[i]);
200+
#else
201+
if (_y_axis_i_ptr != nullptr) {
202+
for (unsigned int i = 0; i < _y_axis_ptr_size; i++)
203+
yAxis.add(_y_axis_i_ptr[i]);
204+
}
205+
#endif
206+
break;
207+
case GraphAxisType::FLOAT:
208+
#if DASH_USE_LEGACY_CHART_STORAGE == 1
209+
for (int i = 0; i < _y_axis_f.size(); i++)
210+
yAxis.add(_y_axis_f[i]);
211+
#else
212+
if (_y_axis_f_ptr != nullptr) {
213+
for (unsigned int i = 0; i < _y_axis_ptr_size; i++)
214+
yAxis.add(_y_axis_f_ptr[i]);
215+
}
216+
#endif
217+
break;
218+
default:
219+
// blank value
220+
break;
221+
}
222+
}
223+
}
224+
225+
Chart::~Chart() {
226+
_dashboard->remove(*this);
227+
}
228+
229+
/*
230+
Card
231+
*/
232+
233+
Card::Card(ESPDash* dashboard, const dash::Component::Type type, const char* name, const char* symbol, const int min, const int max, const int step) : dash::Widget(*dashboard, name, type), _dashboard(dashboard) {
234+
_symbol = symbol ? symbol : "";
235+
_value_min = min;
236+
_value_max = max;
237+
_value_step = step;
238+
setIndex(255);
239+
_value_type = Card::INTEGER;
240+
}
241+
242+
Card::Card(ESPDash* dashboard, const dash::Component::Type type, const char* name, const char* symbol, const float min, const float max, const float step) : dash::Widget(*dashboard, name, type), _dashboard(dashboard) {
243+
_symbol = symbol ? symbol : "";
244+
_value_min_f = min;
245+
_value_max_f = max;
246+
_value_step_f = step;
247+
setIndex(255);
248+
_value_type = Card::FLOAT;
249+
}
250+
251+
void Card::update(int value, const char* symbol) {
252+
if (_value_type == Card::STRING) {
253+
_value_s = "";
254+
}
255+
_value_type = Card::INTEGER;
256+
if (_value_i != value) {
257+
_value_i = value;
258+
setChange(Property::VALUE);
259+
}
260+
if (symbol && _symbol != symbol) {
261+
_symbol = symbol;
262+
setChange(Property::SYMBOL);
263+
}
264+
}
265+
266+
void Card::update(float value, const char* symbol) {
267+
if (_value_type == Card::STRING) {
268+
_value_s = "";
269+
}
270+
_value_type = Card::FLOAT;
271+
if (_value_f != value) {
272+
_value_f = value;
273+
setChange(Property::VALUE);
274+
}
275+
if (symbol && _symbol != symbol) {
276+
_symbol = symbol;
277+
setChange(Property::SYMBOL);
278+
}
279+
}
280+
281+
void Card::update(const char* value, const char* symbol) {
282+
if (_value_type == Card::STRING) {
283+
if (_value_s != value) {
284+
_value_s = value;
285+
setChange(Property::VALUE);
286+
}
287+
}
288+
_value_type = Card::STRING;
289+
if (symbol && _symbol != symbol) {
290+
_symbol = symbol;
291+
setChange(Property::SYMBOL);
292+
}
293+
}
294+
295+
void Card::update(dash::string&& value, const char* symbol) {
296+
if (_value_type == Card::STRING) {
297+
if (_value_s != value)
298+
_value_s = std::move(value);
299+
setChange(Property::VALUE);
300+
}
301+
_value_type = Card::STRING;
302+
if (symbol && _symbol != symbol) {
303+
_symbol = symbol;
304+
setChange(Property::SYMBOL);
305+
}
306+
}
307+
308+
void Card::update(bool value, const char* symbol) {
309+
if (_value_type == Card::STRING) {
310+
_value_s = "";
311+
}
312+
_value_type = Card::INTEGER;
313+
if (_value_i != value) {
314+
_value_i = value;
315+
setChange(Property::VALUE);
316+
}
317+
if (symbol && _symbol != symbol) {
318+
_symbol = symbol;
319+
setChange(Property::SYMBOL);
320+
}
321+
}
322+
323+
void Card::toJson(const JsonObject& json, bool onlyChanges) const {
324+
dash::Widget::toJson(json, onlyChanges);
325+
326+
if (!onlyChanges) {
327+
// Don't add useless values to cards which don't require them
328+
dash::Component::Type type = dash::Widget::type();
329+
if (type == SLIDER_CARD || type == PROGRESS_CARD) {
330+
if (_value_type == Card::FLOAT) {
331+
json["min"] = String(_value_min_f, 2);
332+
json["max"] = String(_value_max_f, 2);
333+
json["step"] = String(_value_step_f, 2);
334+
} else {
335+
json["min"] = _value_min;
336+
json["max"] = _value_max;
337+
if (_value_step != 1)
338+
json["step"] = _value_step;
339+
}
340+
}
341+
}
342+
343+
if (!onlyChanges || hasChanged(Property::SYMBOL))
344+
json["s"] = _symbol;
345+
346+
if (!onlyChanges || hasChanged(Property::VALUE)) {
347+
switch (_value_type) {
348+
case Card::INTEGER:
349+
json["v"] = _value_i;
350+
break;
351+
case Card::FLOAT:
352+
json["v"] = String(_value_f, 2);
353+
break;
354+
case Card::STRING:
355+
if (_value_s.length()) {
356+
json["v"] = _value_s;
357+
}
358+
break;
359+
default:
360+
// blank value
361+
break;
362+
}
363+
}
364+
}
365+
366+
void Card::onEvent(const JsonObject& json) const {
367+
if (_callback && json["command"] == "button:clicked") {
368+
_callback(json["value"].as<int>());
369+
return;
370+
}
371+
372+
if (_callback_f && _value_type == Card::FLOAT && json["command"] == "slider:changed") {
373+
_callback_f(json["value"].as<float>());
374+
return;
375+
}
376+
377+
if (_callback && json["command"] == "slider:changed") {
378+
_callback(json["value"].as<int>());
379+
return;
380+
}
381+
}
382+
383+
Card::~Card() {
384+
_dashboard->remove(*this);
385+
}

‎src/ESPDash.cpp

+179-396
Large diffs are not rendered by default.

‎src/ESPDash.h

+64-98
Original file line numberDiff line numberDiff line change
@@ -14,78 +14,39 @@ Github URL: https://github.com/ayushsharma82/ESP-DASH
1414
#ifndef ESPDash_h
1515
#define ESPDash_h
1616

17-
#include <functional>
18-
#include <stdint.h>
19-
#include <stdlib.h>
20-
#include <string.h>
21-
2217
#include "Arduino.h"
23-
#include "stdlib_noniso.h"
2418
#include "dash_webpage.h"
19+
#include "stdlib_noniso.h"
2520

2621
#if defined(ESP8266)
27-
#define DASH_HARDWARE "ESP8266"
28-
#include "ESP8266WiFi.h"
29-
#include "ESPAsyncTCP.h"
22+
#define DASH_HARDWARE "ESP8266"
23+
#include "ESP8266WiFi.h"
24+
#include "ESPAsyncTCP.h"
3025
#elif defined(ESP32)
31-
#define DASH_HARDWARE "ESP32"
32-
#include "WiFi.h"
33-
#include "AsyncTCP.h"
26+
#define DASH_HARDWARE "ESP32"
27+
#include "AsyncTCP.h"
28+
#include "WiFi.h"
3429
#endif
3530

36-
#define DASH_STATUS_IDLE "i"
37-
#define DASH_STATUS_SUCCESS "s"
38-
#define DASH_STATUS_WARNING "w"
39-
#define DASH_STATUS_DANGER "d"
40-
41-
#include "ESPAsyncWebServer.h"
4231
#include "ArduinoJson.h"
32+
#include "ESPAsyncWebServer.h"
33+
34+
#include "dash/DashDefines.h"
35+
#include "dash/DashCards.h"
36+
#include "dash/DashCharts.h"
37+
#include "dash/DashComponent.h"
38+
#include "dash/DashStatistics.h"
39+
#include "dash/DashWidget.h"
40+
41+
// deprecated classes, still there for backward compatibility
4342
#include "Card.h"
4443
#include "Chart.h"
4544
#include "Statistic.h"
4645

47-
#include <vector>
48-
49-
// If DASH_JSON_SIZE is set to a value, ESP-DASH will frequently measure the Json payload to make sure it remains within this size.
50-
// If the Json payload to send is larger, the payload will be split in several parts and sent in multiple messages.
51-
//
52-
// When this value is set:
53-
// - it should not be too large to avoid sending a big message, which takes longer to send and to build because of the frequent json size measurements. 4096 and 8192 are good values for large dashboards.
54-
// - it should not be too small to avoid sending too many messages, which can slow down the dashboard rendering and fill the websocket message queue. 2048 is a good minimum value.
55-
//
56-
// When using ArduinoJson 7, you can set this value to 0 (by default) to disable the websocket message fragmentation in smaller parts and to disable the measurements, to improve performance.
57-
// This will speed up the rendering, at the expense of risking to exhaust the heap in the case of large dashboard.
58-
// To workaround that, when using DASH_JSON_SIZE == 0 with ArduinoJson 7, you can also set DASH_MIN_FREE_HEAP to a value which is more than the size of the biggest payload for your dashboard.
59-
// For example, if your app is big and has a payload sie of 12kb, then you can set DASH_MIN_FREE_HEAP to 16384 (16kb) to make sure the heap is never exhausted.
60-
// When DASH_MIN_FREE_HEAP is set to a value, you instruct ESP-DASH to check the free heap to make sure there is enough heap to send the payload.
61-
//
62-
// In summary:
63-
//
64-
// - With ArduinoJson 6: DASH_JSON_SIZE should be set to a value greater than 0 and fragmentation is used.
65-
// - With ArduinoJson 7: DASH_JSON_SIZE can be set to 0 to disable the fragmentation and the measurements, at the risk of going out of heap. This option gives the best performance but you need to make sure to have enough heap in case your application is large.
66-
// - With ArduinoJson 7: if DASH_JSON_SIZE is set to 0 and DASH_MIN_FREE_HEAP is set to a value greater than 0, heap will be checked before sending the payload and fragmentation will trigger if not enough heap..
67-
// - DASH_MIN_FREE_HEAP will have no effect is DASH_JSON_SIZE is not set to 0
68-
//
69-
// To help you decide, you can uncomment line 543 in the cpp which will display the free heap size and teh required heap size to build the websocket message.
70-
// ESP_LOGW("ESPDash", "Required Heap size to build WebSocket message: %d bytes. Free Heap: %" PRIu32 " bytes", len, ESP.getFreeHeap());
46+
// Controls the payload size: as soon as the payload size reaches this value, the payload is sent to the client
47+
// This allows to split in batches the payload to avoid sending too large payloads at once
7148
#ifndef DASH_JSON_SIZE
72-
#if ARDUINOJSON_VERSION_MAJOR == 6 && !defined(DASH_JSON_DOCUMENT_ALLOCATION)
73-
#define DASH_JSON_SIZE 2048
74-
#else
75-
#define DASH_JSON_SIZE 0
76-
#endif // ARDUINOJSON_VERSION_MAJOR == 6 && !defined(DASH_JSON_DOCUMENT_ALLOCATION)
77-
#endif // DASH_JSON_SIZE
78-
79-
// Only for ArduinoJson 7
80-
#ifndef DASH_MIN_FREE_HEAP
81-
#define DASH_MIN_FREE_HEAP 0
82-
#endif
83-
84-
#if ARDUINOJSON_VERSION_MAJOR == 6 && !defined(DASH_JSON_DOCUMENT_ALLOCATION)
85-
#if DASH_JSON_SIZE == 0
86-
#error "DASH_JSON_SIZE must be set to a value greater than 0 when using ArduinoJson 6"
87-
#endif
88-
#define DASH_JSON_DOCUMENT_ALLOCATION DASH_JSON_SIZE * 3
49+
#define DASH_JSON_SIZE 2048
8950
#endif
9051

9152
#ifndef DASH_USE_LEGACY_CHART_STORAGE
@@ -96,13 +57,8 @@ Github URL: https://github.com/ayushsharma82/ESP-DASH
9657
#define DASH_MAX_WS_CLIENTS DEFAULT_MAX_WS_CLIENTS
9758
#endif
9859

99-
// Forward Declaration
100-
class Card;
101-
class Chart;
102-
class Statistic;
103-
10460
// ESPDASH Class
105-
class ESPDash{
61+
class ESPDash {
10662
public:
10763
// changes_only: true (equivalent to sendUpdates(false)) - when sending updates to the client
10864
// changes_only: false (equivalent to sendUpdates(true)) - when sending the entire layout to the client or when forcing a full update
@@ -112,10 +68,8 @@ class ESPDash{
11268
AsyncWebServer* _server = nullptr;
11369
AsyncWebSocket* _ws = nullptr;
11470

115-
std::vector<Card*> cards;
116-
std::vector<Chart*> charts;
117-
std::vector<Statistic*> statistics;
118-
bool default_stats_enabled = false;
71+
std::list<dash::Component*> _components; // all components
72+
std::list<dash::Component*> _componentsOwned; // components created by ESPDash (like statistics), which should be deleted in the destructor
11973
bool basic_auth = false;
12074
dash::string username;
12175
dash::string password;
@@ -125,49 +79,34 @@ class ESPDash{
12579
volatile bool _asyncAccessInProgress = false;
12680

12781
// Generate layout json
128-
void generateLayoutJSON(AsyncWebSocketClient* client, bool changes_only = false, Card* onlyCard = nullptr, Chart* onlyChart = nullptr);
82+
void generateLayoutJSON(AsyncWebSocketClient* client, bool changes_only, const dash::Component* onlyComponent);
83+
size_t generateLayoutJSON(AsyncWebSocketClient* client, bool changes_only, const dash::Component* onlyComponent, JsonDocument& doc, dash::Component::Family family);
12984
void send(AsyncWebSocketClient* client, JsonDocument& doc);
130-
bool overflowed(JsonDocument& doc);
13185

132-
// Generate Component JSON
133-
void generateComponentJSON(JsonObject& obj, Card* card, bool change_only = false);
134-
void generateComponentJSON(JsonObject& obj, Chart* chart, bool change_only = false);
86+
static const char* jsonKey(const dash::Component* c) { return jsonKey(c->family()); }
87+
static const char* jsonKey(dash::Component::Family family);
13588

13689
public:
137-
ESPDash(AsyncWebServer* server, const char* uri, bool enable_default_stats = true);
138-
ESPDash(AsyncWebServer* server, bool enable_default_stats);
139-
ESPDash(AsyncWebServer* server);
90+
ESPDash(AsyncWebServer& server, const char* uri, bool enable_default_stats);
91+
ESPDash(AsyncWebServer& server, bool enable_default_stats) : ESPDash(server, "/", enable_default_stats) {}
92+
ESPDash(AsyncWebServer& server) : ESPDash(server, "/", true) {}
14093

14194
// Set Authentication
14295
void setAuthentication(const char* user, const char* pass);
143-
void setAuthentication(const dash::string &user, const dash::string &pass);
96+
void setAuthentication(const dash::string& user, const dash::string& pass) { setAuthentication(user.c_str(), pass.c_str()); }
14497

145-
// Add Card
146-
void add(Card *card);
147-
// Remove Card
148-
void remove(Card *card);
149-
150-
// Add Chart
151-
void add(Chart *card);
152-
// Remove Chart
153-
void remove(Chart *card);
154-
155-
// Add Statistic
156-
void add(Statistic *statistic);
157-
// Remove Statistic
158-
void remove(Statistic *statistic);
98+
// Add a component to the dashboard and return true if the component was added, false if the component ID was already present
99+
bool add(dash::Component& component);
100+
void remove(dash::Component& component);
159101

160102
// Notify client side to update values
161103
void sendUpdates(bool force = false);
162104
void refreshLayout() { sendUpdates(true); }
163-
void refreshCard(Card *card);
164-
void refreshChart(Chart* chart);
105+
void refresh(const dash::Component& component);
165106

166-
uint32_t nextId();
107+
bool hasClient() { return _ws->count() > 0; }
167108

168-
bool hasClient();
169-
170-
// can be used to check if the async_http task might currently access the cards data,
109+
// can be used to check if the async_http task might currently access the cards data,
171110
// in which case you should not modify them
172111
bool isAsyncAccessInProgress() { return _asyncAccessInProgress; }
173112

@@ -178,6 +117,33 @@ class ESPDash{
178117
void onBeforeUpdate(BeforeUpdateCallback callback) { _beforeUpdateCallback = callback; }
179118

180119
~ESPDash();
120+
121+
// deprecations
122+
#pragma GCC diagnostic push
123+
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
124+
[[deprecated("use ESPDash(AsyncWebServer&, const char*, bool) instead")]]
125+
ESPDash(AsyncWebServer* server, const char* uri, bool enable_default_stats) : ESPDash(*server, uri, enable_default_stats) {}
126+
[[deprecated("use ESPDash(AsyncWebServer&, bool) instead")]]
127+
ESPDash(AsyncWebServer* server, bool enable_default_stats) : ESPDash(*server, enable_default_stats) {}
128+
[[deprecated("use ESPDash(AsyncWebServer&) instead")]]
129+
ESPDash(AsyncWebServer* server) : ESPDash(*server) {}
130+
[[deprecated("use add(dash::Component&) instead")]]
131+
void add(Card* card) { add(*card); }
132+
[[deprecated("use remove(dash::Component&) instead")]]
133+
void remove(Card* card) { remove(*card); }
134+
[[deprecated("use add(dash::Component&) instead")]]
135+
void add(Chart* chart) { add(*chart); }
136+
[[deprecated("use remove(dash::Component&) instead")]]
137+
void remove(Chart* chart) { remove(*chart); }
138+
[[deprecated("use add(dash::Component&) instead")]]
139+
void add(Statistic* statistic) { add(*statistic); }
140+
[[deprecated("use remove(dash::Component&) instead")]]
141+
void remove(Statistic* statistic) { remove(*statistic); }
142+
[[deprecated("use refresh(dash::Component&) instead")]]
143+
void refreshCard(Card* card) { refresh(*card); }
144+
[[deprecated("use refresh(dash::Component&) instead")]]
145+
void refreshChart(Chart* chart) { refresh(*chart); }
146+
#pragma GCC diagnostic pop
181147
};
182148

183149
#endif

‎src/Statistic.cpp

-30
This file was deleted.

‎src/Statistic.h

+12-36
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,18 @@
11
#ifndef __STAT_H
22
#define __STAT_H
33

4-
#include <functional>
5-
#include "Arduino.h"
6-
7-
#include "ESPDash.h"
8-
#include "ArduinoJson.h"
9-
10-
#ifdef DASH_USE_STL_STRING
11-
#include <string>
12-
namespace dash {
13-
using string = std::string;
14-
}
15-
#else
16-
namespace dash {
17-
using string = String;
18-
}
19-
#endif
20-
21-
// Forward Declaration
22-
class ESPDash;
23-
24-
class Statistic {
25-
private:
26-
ESPDash *_dashboard;
27-
uint32_t _id;
28-
const char *_key;
29-
dash::string _value;
30-
bool _changed = false;
31-
32-
public:
33-
Statistic(ESPDash *dashboard, const char *key, const char *value = "");
34-
void set(const char *value);
35-
void set(const dash::string& value) { set(value.c_str()); }
36-
void set(dash::string&& value);
37-
~Statistic();
38-
39-
friend class ESPDash;
4+
#include "dash/DashStatistics.h"
5+
6+
class [[deprecated("This class is deprecated. Use dash::StatisticValue instead.")]] Statistic : public dash::StatisticValue<dash::string> {
7+
private:
8+
ESPDash* _dashboard;
9+
10+
public:
11+
Statistic(ESPDash* dashboard, const char* key, const char* value = "") : dash::StatisticValue<dash::string>(*dashboard, key), _dashboard(dashboard) { set(value); }
12+
bool set(const char* value) { return dash::StatisticValue<dash::string>::setValue(dash::string(value)); }
13+
void set(const dash::string& value) { set(value.c_str()); }
14+
void set(dash::string&& value) { dash::StatisticValue<dash::string>::setValue(std::move(value)); }
15+
~Statistic();
4016
};
4117

4218
#endif

‎src/dash/DashCards.h

+379
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
#pragma once
2+
3+
#include "DashWidget.h"
4+
5+
namespace dash {
6+
enum class Status {
7+
NONE,
8+
IDLE,
9+
SUCCESS,
10+
WARNING,
11+
DANGER,
12+
};
13+
14+
// this is the base class for all cards
15+
class Card : public Widget {
16+
public:
17+
virtual ~Card() = default;
18+
19+
virtual void toJson(const JsonObject& json, bool onlyChanges) const override {
20+
Widget::toJson(json, onlyChanges);
21+
}
22+
23+
protected:
24+
// construct a new card and add it to the dashboard
25+
Card(ESPDash& dashboard, const char* name, Type type) : Widget(dashboard, name, type) {
26+
setIndex(255);
27+
}
28+
// construct a new card without adding it to any dashboard
29+
Card(const char* name, Type type) : Widget(name, type) {
30+
setIndex(255);
31+
}
32+
33+
// status names
34+
static const char* _statusName(Status status) {
35+
switch (status) {
36+
case Status::NONE:
37+
return "";
38+
case Status::IDLE:
39+
return "i";
40+
case Status::SUCCESS:
41+
return "s";
42+
case Status::WARNING:
43+
return "w";
44+
case Status::DANGER:
45+
return "d";
46+
default:
47+
assert(false);
48+
return "";
49+
}
50+
}
51+
};
52+
53+
// this is a card holding a value
54+
template <typename T, uint8_t Precision = 2>
55+
class ValueCard : public Card {
56+
public:
57+
virtual ~ValueCard() = default;
58+
59+
bool hasValue() const { return _value.has_value(); }
60+
const T& value() const { return _value.value(); }
61+
const std::optional<T>& optional() const { return _value; }
62+
63+
virtual bool setValue(const T& value) {
64+
if (_value == value)
65+
return false;
66+
_value = value;
67+
setChange(Property::VALUE);
68+
return true;
69+
}
70+
71+
virtual bool setValue(T&& value) {
72+
if (_value == value)
73+
return false;
74+
_value = std::forward<T>(value);
75+
setChange(Property::VALUE);
76+
return true;
77+
}
78+
79+
virtual bool setOptionalValue(const std::optional<T>& value) {
80+
return value.has_value() ? setValue(value.value()) : removeValue();
81+
}
82+
83+
virtual bool setOptionalValue(std::optional<T>&& value) {
84+
return value.has_value() ? setValue(value.value()) : removeValue();
85+
}
86+
87+
virtual bool removeValue() {
88+
if (!_value.has_value())
89+
return false;
90+
_value.reset();
91+
setChange(Property::VALUE);
92+
return true;
93+
}
94+
95+
virtual void toJson(const JsonObject& json, bool onlyChanges) const override {
96+
Card::toJson(json, onlyChanges);
97+
if (!onlyChanges || hasChanged(Property::VALUE)) {
98+
if (_value.has_value())
99+
toJsonValue<T, Precision>(json["v"].to<JsonVariant>(), _value.value());
100+
else
101+
json["v"] = "";
102+
}
103+
}
104+
105+
protected:
106+
ValueCard(ESPDash& dashboard, const char* name, Type type) : Card(dashboard, name, type) {}
107+
ValueCard(const char* name, Type type) : Card(name, type) {}
108+
109+
private:
110+
std::optional<T> _value;
111+
};
112+
113+
// generic card
114+
template <typename T = dash::string, uint8_t Precision = 2, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T> || std::is_same_v<T, dash::string> || std::is_same_v<T, const char*>, bool> = true>
115+
class GenericCard : public ValueCard<T, Precision> {
116+
public:
117+
GenericCard(ESPDash& dashboard, const char* name, const char* symbol = "") : ValueCard<T, Precision>(dashboard, name, Component::Type::CARD_GENERIC), _symbol(symbol) {}
118+
GenericCard(const char* name, const char* symbol = "") : ValueCard<T, Precision>(name, Component::Type::CARD_GENERIC), _symbol(symbol) {}
119+
virtual ~GenericCard() = default;
120+
121+
const char* symbol() const { return _symbol; }
122+
123+
bool setSymbol(const char* symbol) {
124+
if (strcmp(_symbol, symbol) == 0)
125+
return false;
126+
_symbol = symbol;
127+
ValueCard<T, Precision>::setChange(dash::Component::Property::SYMBOL);
128+
return true;
129+
}
130+
131+
virtual void toJson(const JsonObject& json, bool onlyChanges) const override {
132+
ValueCard<T, Precision>::toJson(json, onlyChanges);
133+
if (!onlyChanges || ValueCard<T, Precision>::hasChanged(dash::Component::Property::SYMBOL))
134+
json["s"] = _symbol;
135+
}
136+
137+
private:
138+
const char* _symbol;
139+
};
140+
141+
// temperature card
142+
template <typename T = float, uint8_t Precision = 2, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, bool> = true>
143+
class TemperatureCard : public ValueCard<float, Precision> {
144+
public:
145+
TemperatureCard(ESPDash& dashboard, const char* name, const char* unit = "°C") : ValueCard<T, Precision>(dashboard, name, dash::Component::Type::CARD_TEMPERATURE), _unit(unit) {}
146+
TemperatureCard(const char* name, const char* unit = "°C") : ValueCard<T, Precision>(name, dash::Component::Type::CARD_TEMPERATURE), _unit(unit) {}
147+
148+
const char* unit() const { return _unit; }
149+
150+
bool setUnit(const char* unit) {
151+
if (strcmp(_unit, unit) == 0)
152+
return false;
153+
_unit = unit;
154+
ValueCard<T, Precision>::setChange(dash::Component::Property::SYMBOL);
155+
return true;
156+
}
157+
158+
virtual void toJson(const JsonObject& json, bool onlyChanges) const override {
159+
ValueCard<T, Precision>::toJson(json, onlyChanges);
160+
if (!onlyChanges || ValueCard<T, Precision>::hasChanged(dash::Component::Property::SYMBOL))
161+
json["s"] = _unit;
162+
}
163+
164+
private:
165+
const char* _unit;
166+
};
167+
168+
// humidity card
169+
template <typename T = float, uint8_t Precision = 2, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, bool> = true>
170+
class HumidityCard : public ValueCard<T, Precision> {
171+
public:
172+
HumidityCard(ESPDash& dashboard, const char* name, const char* unit = "%") : ValueCard<T, Precision>(dashboard, name, dash::Component::Type::CARD_HUMIDITY), _unit(unit) {}
173+
HumidityCard(const char* name, const char* unit = "%") : ValueCard<T, Precision>(name, dash::Component::Type::CARD_HUMIDITY), _unit(unit) {}
174+
175+
const char* unit() const { return _unit; }
176+
177+
bool setUnit(const char* unit) {
178+
if (strcmp(_unit, unit) == 0)
179+
return false;
180+
_unit = unit;
181+
ValueCard<T, Precision>::setChange(dash::Component::Property::SYMBOL);
182+
return true;
183+
}
184+
185+
virtual void toJson(const JsonObject& json, bool onlyChanges) const override {
186+
ValueCard<T, Precision>::toJson(json, onlyChanges);
187+
if (!onlyChanges || ValueCard<T, Precision>::hasChanged(dash::Component::Property::SYMBOL))
188+
json["s"] = _unit;
189+
}
190+
191+
private:
192+
const char* _unit;
193+
};
194+
195+
// feedback card
196+
template <typename T = dash::string, std::enable_if_t<std::is_same_v<T, dash::string> || std::is_same_v<T, const char*>, bool> = true>
197+
class FeedbackCard : public ValueCard<T> {
198+
public:
199+
FeedbackCard(ESPDash& dashboard, const char* name, Status initialStatus = Status::NONE, const char* initialMessage = "") : ValueCard<T>(dashboard, name, dash::Component::Type::CARD_STATUS), _status(initialStatus) {
200+
setMessage(initialMessage);
201+
}
202+
FeedbackCard(const char* name, Status initialStatus = Status::NONE, const char* initialMessage = "") : ValueCard<T>(name, dash::Component::Type::CARD_STATUS), _status(initialStatus) {
203+
setMessage(initialMessage);
204+
}
205+
virtual ~FeedbackCard() = default;
206+
207+
Status status() const { return _status; }
208+
209+
bool setStatus(Status status) {
210+
if (_status == status)
211+
return false;
212+
_status = status;
213+
ValueCard<T>::setChange(Component::Property::SYMBOL);
214+
return true;
215+
}
216+
217+
bool setMessage(const char* message) { return ValueCard<T>::setValue(message); }
218+
219+
bool setFeedback(const T& message, Status status) { return setStatus(status) | ValueCard<T>::setValue(message); }
220+
bool setFeedback(T&& message, Status status) { return setStatus(status) | ValueCard<T>::setValue(std::move(message)); }
221+
222+
virtual void toJson(const JsonObject& json, bool onlyChanges) const override {
223+
ValueCard<T>::toJson(json, onlyChanges);
224+
if (!onlyChanges || ValueCard<T>::hasChanged(Component::Property::SYMBOL))
225+
json["s"] = Card::_statusName(_status);
226+
}
227+
228+
private:
229+
Status _status;
230+
};
231+
232+
// switch card
233+
class SwitchCard : public ValueCard<bool> {
234+
public:
235+
SwitchCard(ESPDash& dashboard, const char* name) : ValueCard<bool>(dashboard, name, dash::Component::Type::CARD_BUTTON) {}
236+
SwitchCard(const char* name) : ValueCard<bool>(name, dash::Component::Type::CARD_BUTTON) {}
237+
virtual ~SwitchCard() = default;
238+
239+
bool toggle() { return ValueCard<bool>::setValue(!ValueCard<bool>::optional().value_or(false)); }
240+
bool on() { return ValueCard<bool>::setValue(true); }
241+
bool off() { return ValueCard<bool>::setValue(false); }
242+
243+
void onChange(std::function<void(bool state)> callback) { _callback = callback; }
244+
245+
void onEvent(const JsonObject& json) const override {
246+
if (_callback)
247+
_callback(json["value"].as<bool>());
248+
}
249+
250+
virtual void toJson(const JsonObject& json, bool onlyChanges) const override {
251+
ValueCard<bool>::toJson(json, onlyChanges);
252+
if (!onlyChanges || hasChanged(Property::VALUE))
253+
json["v"] = optional().value_or(false) ? 1 : 0;
254+
}
255+
256+
private:
257+
std::function<void(bool state)> _callback = nullptr;
258+
};
259+
260+
// progress card
261+
template <typename T = int, uint8_t Precision = 2, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, bool> = true>
262+
class ProgressCard : public ValueCard<T, Precision> {
263+
public:
264+
ProgressCard(ESPDash& dashboard, const char* name, T minValue, T maxValue, const char* unit = "") : ProgressCard(dashboard, name, dash::Component::Type::CARD_PROGRESS, minValue, maxValue, unit) {}
265+
ProgressCard(const char* name, T minValue, T maxValue, const char* unit = "") : ProgressCard(name, dash::Component::Type::CARD_PROGRESS, minValue, maxValue, unit) {}
266+
virtual ~ProgressCard() = default;
267+
268+
T min() const { return _minValue; }
269+
T max() const { return _maxValue; }
270+
const char* unit() const { return _unit; }
271+
272+
bool setUnit(const char* unit) {
273+
if (strcmp(_unit, unit) == 0)
274+
return false;
275+
_unit = unit;
276+
ValueCard<T, Precision>::setChange(dash::Component::Property::SYMBOL);
277+
return true;
278+
}
279+
280+
bool setMin(T minValue) {
281+
if (_minValue == minValue)
282+
return false;
283+
_minValue = minValue;
284+
ValueCard<T, Precision>::setChange(dash::Component::Property::MIN);
285+
if (ValueCard<T, Precision>::hasValue() && ValueCard<T, Precision>::value() < _minValue)
286+
ValueCard<T, Precision>::setValue(_minValue);
287+
return true;
288+
}
289+
290+
bool setMax(T maxValue) {
291+
if (_maxValue == maxValue)
292+
return false;
293+
_maxValue = maxValue;
294+
ValueCard<T, Precision>::setChange(dash::Component::Property::MAX);
295+
if (ValueCard<T, Precision>::hasValue() && ValueCard<T, Precision>::value() > _maxValue)
296+
ValueCard<T, Precision>::setValue(_maxValue);
297+
return true;
298+
}
299+
300+
virtual bool setValue(const T& value) override {
301+
if (value < _minValue)
302+
return ValueCard<T, Precision>::setValue(_minValue);
303+
if (value > _maxValue)
304+
return ValueCard<T, Precision>::setValue(_maxValue);
305+
return ValueCard<T, Precision>::setValue(value);
306+
}
307+
308+
virtual bool setValue(T&& value) override {
309+
if (value < _minValue)
310+
return ValueCard<T, Precision>::setValue(_minValue);
311+
if (value > _maxValue)
312+
return ValueCard<T, Precision>::setValue(_maxValue);
313+
return ValueCard<T, Precision>::setValue(std::forward<T>(value));
314+
}
315+
316+
virtual void toJson(const JsonObject& json, bool onlyChanges) const override {
317+
ValueCard<T, Precision>::toJson(json, onlyChanges);
318+
if (!onlyChanges || ValueCard<T, Precision>::hasChanged(dash::Component::Property::SYMBOL))
319+
json["s"] = _unit;
320+
if (!onlyChanges || ValueCard<T, Precision>::hasChanged(dash::Component::Property::MIN))
321+
dash::toJsonValue<T, Precision>(json["min"].to<JsonVariant>(), _minValue);
322+
if (!onlyChanges || ValueCard<T, Precision>::hasChanged(dash::Component::Property::MAX))
323+
dash::toJsonValue<T, Precision>(json["max"].to<JsonVariant>(), _maxValue);
324+
}
325+
326+
protected:
327+
ProgressCard(ESPDash& dashboard, const char* name, dash::Component::Type type, T minValue, T maxValue, const char* unit = "") : ValueCard<T, Precision>(dashboard, name, type), _minValue(minValue), _maxValue(maxValue), _unit(unit) {}
328+
ProgressCard(const char* name, dash::Component::Type type, T minValue, T maxValue, const char* unit = "") : ValueCard<T, Precision>(name, type), _minValue(minValue), _maxValue(maxValue), _unit(unit) {}
329+
330+
private:
331+
T _minValue;
332+
T _maxValue;
333+
const char* _unit;
334+
};
335+
336+
// slider card
337+
template <typename T = int, uint8_t Precision = 2, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, bool> = true>
338+
class SliderCard : public ProgressCard<T, Precision> {
339+
public:
340+
SliderCard(ESPDash& dashboard, const char* name, T minValue, T maxValue, T step, const char* unit = "") : ProgressCard<T, Precision>(dashboard, name, dash::Component::Type::CARD_SLIDER, minValue, maxValue, unit), _step(step) {}
341+
SliderCard(const char* name, T minValue, T maxValue, T step, const char* unit = "") : ProgressCard<T, Precision>(name, dash::Component::Type::CARD_SLIDER, minValue, maxValue, unit), _step(step) {}
342+
virtual ~SliderCard() = default;
343+
344+
T step() const { return _step; }
345+
346+
bool setStep(T step) {
347+
if (_step == step)
348+
return false;
349+
_step = step;
350+
ProgressCard<T, Precision>::setChange(dash::Component::Property::STEP);
351+
return true;
352+
}
353+
354+
void onChange(std::function<void(T value)> callback) { _callback = callback; }
355+
356+
virtual void onEvent(const JsonObject& json) const override {
357+
if (_callback)
358+
_callback(json["value"].as<T>());
359+
}
360+
361+
virtual void toJson(const JsonObject& json, bool onlyChanges) const override {
362+
ProgressCard<T, Precision>::toJson(json, onlyChanges);
363+
if (!onlyChanges || ValueCard<T, Precision>::hasChanged(dash::Component::Property::STEP))
364+
dash::toJsonValue<T, Precision>(json["step"].to<JsonVariant>(), _step);
365+
}
366+
367+
private:
368+
T _step;
369+
std::function<void(T value)> _callback = nullptr;
370+
};
371+
372+
template <typename T = uint8_t, uint8_t Precision = 2, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, bool> = true>
373+
class PercentageSliderCard : public SliderCard<T> {
374+
public:
375+
PercentageSliderCard(ESPDash& dashboard, const char* name) : SliderCard<T>(dashboard, name, 0, 100, 1, "%") {}
376+
PercentageSliderCard(const char* name) : SliderCard<T>(name, 0, 100, 1, "%") {}
377+
virtual ~PercentageSliderCard() = default;
378+
};
379+
} // namespace dash

‎src/dash/DashCharts.h

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#pragma once
2+
3+
#include "DashWidget.h"
4+
5+
namespace dash {
6+
template <typename X, typename Y, std::enable_if_t<(std::is_integral_v<X> || std::is_floating_point_v<X> || std::is_same_v<X, dash::string> || std::is_same_v<X, const char*>) && (std::is_integral_v<Y> || std::is_floating_point_v<Y>), bool> = true>
7+
class Chart : public Widget {
8+
public:
9+
virtual ~Chart() = default;
10+
11+
const X* x() const { return _x_axis_i_ptr; }
12+
const Y* y() const { return _y_axis_i_ptr; }
13+
size_t xSize() const { return _x_axis_ptr_size; }
14+
size_t ySize() const { return _y_axis_ptr_size; }
15+
16+
// set the x-axis values
17+
bool setX(X arr_x[], size_t x_size) {
18+
_x_axis_i_ptr = arr_x;
19+
_x_axis_ptr_size = x_size;
20+
setChange(Property::AXIS_X);
21+
return true;
22+
}
23+
24+
// set the y-axis values
25+
bool setY(Y arr_y[], size_t y_size) {
26+
_y_axis_i_ptr = arr_y;
27+
_y_axis_ptr_size = y_size;
28+
setChange(Property::AXIS_Y);
29+
return true;
30+
}
31+
32+
virtual void toJson(const JsonObject& json, bool onlyChanges) const override {
33+
Widget::toJson(json, onlyChanges);
34+
if (!onlyChanges || hasChanged(Property::AXIS_X)) {
35+
JsonArray xAxis = json["x"].to<JsonArray>();
36+
for (size_t i = 0; i < _x_axis_ptr_size; i++)
37+
xAxis.add(_x_axis_i_ptr[i]);
38+
}
39+
if (!onlyChanges || hasChanged(Property::AXIS_Y)) {
40+
JsonArray yAxis = json["y"].to<JsonArray>();
41+
for (size_t i = 0; i < _y_axis_ptr_size; i++)
42+
yAxis.add(_y_axis_i_ptr[i]);
43+
}
44+
}
45+
46+
protected:
47+
// construct a new chart and add it to the dashboard
48+
Chart(ESPDash& dashboard, const char* name, Type type) : Widget(dashboard, name, type) {}
49+
// construct a new chart without adding it to any dashboard
50+
Chart(const char* name, Type type) : Widget(name, type) {}
51+
52+
private:
53+
X* _x_axis_i_ptr = nullptr;
54+
Y* _y_axis_i_ptr = nullptr;
55+
size_t _x_axis_ptr_size = 0;
56+
size_t _y_axis_ptr_size = 0;
57+
};
58+
59+
// bar chart
60+
template <typename X, typename Y, std::enable_if_t<(std::is_integral_v<X> || std::is_floating_point_v<X> || std::is_same_v<X, dash::string> || std::is_same_v<X, const char*>) && (std::is_integral_v<Y> || std::is_floating_point_v<Y>), bool> = true>
61+
class BarChart : public Chart<X, Y> {
62+
public:
63+
BarChart(ESPDash& dashboard, const char* name) : Chart<X, Y>(dashboard, name, Component::Type::CHART_BAR) {}
64+
BarChart(const char* name) : Chart<X, Y>(name, Component::Type::CHART_BAR) {}
65+
virtual ~BarChart() = default;
66+
};
67+
} // namespace dash

‎src/dash/DashComponent.h

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#pragma once
2+
3+
#include "DashDefines.h"
4+
5+
namespace dash {
6+
class Component {
7+
public:
8+
// general component type
9+
enum class Family {
10+
CARD,
11+
CHART,
12+
STATISTIC,
13+
};
14+
15+
// component sub-type
16+
enum class Type {
17+
// cards
18+
CARD_BUTTON,
19+
CARD_GENERIC,
20+
CARD_HUMIDITY,
21+
CARD_PROGRESS,
22+
CARD_SLIDER,
23+
CARD_STATUS,
24+
CARD_TEMPERATURE,
25+
// charts
26+
CHART_BAR,
27+
// statistics
28+
STATISTIC_PROVIDER,
29+
STATISTIC_VALUE,
30+
};
31+
32+
// describes the component fields that can be changed
33+
enum Property : uint8_t {
34+
AXIS_X = 1,
35+
AXIS_Y,
36+
INDEX,
37+
MAX,
38+
MIN,
39+
NAME, // name or title
40+
STEP,
41+
SYMBOL,
42+
VALUE,
43+
};
44+
45+
virtual ~Component() = default;
46+
47+
// component ID
48+
uint16_t id() const { return _id; }
49+
// component name or title
50+
const char* name() const { return _name; }
51+
// component type
52+
Type type() const { return _type; }
53+
// check if the component belongs to a specific family (card, chart, statistic, tab)
54+
bool is(Family family) const {
55+
switch (family) {
56+
case Family::CARD:
57+
return _type >= Type::CARD_BUTTON && _type <= Type::CARD_TEMPERATURE;
58+
case Family::CHART:
59+
return _type == Type::CHART_BAR;
60+
case Family::STATISTIC:
61+
return _type >= Type::STATISTIC_PROVIDER && _type <= Type::STATISTIC_VALUE;
62+
default:
63+
return false;
64+
}
65+
}
66+
// get the component family
67+
Family family() const {
68+
if (is(Family::CARD))
69+
return Family::CARD;
70+
if (is(Family::CHART))
71+
return Family::CHART;
72+
if (is(Family::STATISTIC))
73+
return Family::STATISTIC;
74+
// should never happen => crash
75+
assert(false);
76+
return Family::CARD;
77+
}
78+
79+
// check if one of the component property has changed
80+
bool hasChanged() const { return _bitset >> 1; }
81+
// check if a specific component property has changed
82+
bool hasChanged(Property property) const { return _bitset & (0b1 << property); }
83+
// clear all component change flags.
84+
// hasChanged() will return false after this call
85+
void clearChanges() { _bitset &= 0b1; }
86+
87+
// change component name (not all components support a name change)
88+
bool setName(const char* name) {
89+
if (strcmp(_name, name) == 0)
90+
return false;
91+
_name = name;
92+
setChange(Property::NAME);
93+
return true;
94+
}
95+
96+
// auto-update the component internally
97+
// some components like StatisticProvider are capable of automatically updating their values before a dashboard refresh
98+
virtual bool selfUpdate() { return false; }
99+
100+
virtual void toJson(const JsonObject& json, bool onlyChanges) const {
101+
switch (family()) {
102+
case Family::CARD:
103+
case Family::CHART:
104+
json["id"] = id();
105+
if (!onlyChanges || hasChanged(Property::NAME))
106+
json["n"] = name();
107+
break;
108+
case Family::STATISTIC:
109+
json["i"] = id();
110+
if (!onlyChanges || hasChanged(Property::NAME))
111+
json["k"] = name();
112+
break;
113+
default:
114+
assert(false);
115+
break;
116+
}
117+
}
118+
119+
virtual void onEvent(__unused const JsonObject& json) const {}
120+
121+
protected:
122+
// construct a new component without adding it to any dashboard
123+
Component(const char* name, Type type) : _id(nextId()), _name(name), _type(type) {}
124+
// construct a new component and add it to the dashboard
125+
Component(ESPDash& dashboard, const char* name, Type type);
126+
127+
// mark a component property as changed
128+
void setChange(Property property) {
129+
130+
#ifdef DASH_DEBUG
131+
DASH_LOGD("DashComponent", "[%d] %s : property changed: %d", id(), name(), property);
132+
#endif
133+
_bitset |= (0b1 << property);
134+
}
135+
136+
// generate a new unique component ID
137+
static uint16_t nextId() {
138+
static uint16_t _IDS = 0;
139+
return _IDS++;
140+
}
141+
142+
private:
143+
const uint16_t _id; // component ID
144+
const char* _name; // component name
145+
const Type _type; // component type
146+
uint16_t _bitset = 0b1; // display is bit 0, bits 1-12 to track component changes
147+
};
148+
} // namespace dash

‎src/dash/DashDefines.h

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#pragma once
2+
3+
// set to 1 to see debug logs
4+
// keep to 0 for production: logging impacts performance
5+
// #define DASH_DEBUG 1
6+
7+
#ifdef ESP8266
8+
#include <ets_sys.h>
9+
#define DASH_LOGD(tag, format, ...) ets_printf("DEBUG [%s] " format "\n", tag, ##__VA_ARGS__)
10+
#define DASH_LOGW(tag, format, ...) ets_printf("WARN [%s] " format "\n", tag, ##__VA_ARGS__)
11+
#else
12+
#include <esp32-hal-log.h>
13+
#define DASH_LOGD ESP_LOGD
14+
#define DASH_LOGW ESP_LOGW
15+
#endif
16+
17+
#include <ArduinoJson.h>
18+
#include <inttypes.h>
19+
#include <sys/time.h>
20+
21+
#include <charconv>
22+
#include <functional>
23+
#include <list>
24+
#include <optional>
25+
#include <string>
26+
27+
class ESPDash;
28+
29+
namespace dash {
30+
#ifdef DASH_USE_STL_STRING
31+
using string = std::string;
32+
#else
33+
using string = String;
34+
#endif
35+
36+
// function to convert an unknown type T to a JSON value by applying the correct precision for floats and doubles
37+
template <typename T, uint8_t Precision = 2>
38+
static bool toJsonValue(const JsonVariant& json, const T& value) {
39+
if constexpr (std::is_same_v<T, float>) {
40+
return json.set(String(static_cast<float>(value), (size_t)Precision));
41+
} else if constexpr (std::is_same_v<T, double>) {
42+
return json.set(String(static_cast<double>(value), (size_t)Precision));
43+
} else {
44+
// convert unknown other types eventually using convertToJson functions
45+
return json.set(value);
46+
}
47+
}
48+
49+
// dash::to_string
50+
51+
template <typename T, uint8_t Precision = 2, std::enable_if_t<std::is_same_v<T, dash::string>, bool> = true>
52+
static const dash::string& to_string(const T& value) { return value; }
53+
54+
template <typename T, uint8_t Precision = 2, std::enable_if_t<std::is_same_v<T, const char*>, bool> = true>
55+
static dash::string to_string(const T& value) { return value; }
56+
57+
template <typename T, uint8_t Precision = 2, std::enable_if_t<std::is_integral_v<T>, bool> = true>
58+
static dash::string to_string(const T& value) {
59+
#ifdef DASH_USE_STL_STRING
60+
return std::to_string(value);
61+
#else
62+
return String(value);
63+
#endif
64+
}
65+
66+
template <typename T, uint8_t Precision = 2, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
67+
static dash::string to_string(const T& value) {
68+
#ifdef DASH_USE_STL_STRING
69+
return String(value, (size_t)Precision).c_str();
70+
#else
71+
return String(value, (size_t)Precision);
72+
#endif
73+
}
74+
75+
// dash::string_as
76+
77+
template <typename T, std::enable_if_t<std::is_same_v<T, dash::string> || std::is_same_v<T, const char*>, bool> = true>
78+
static std::optional<T> string_as(const char* str) {
79+
if (str == nullptr)
80+
return std::nullopt;
81+
return str;
82+
}
83+
84+
template <typename T, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, bool> = true>
85+
static std::optional<T> string_as(const char* str) {
86+
auto value = T{};
87+
size_t len = strlen(str);
88+
if (!len)
89+
return std::nullopt;
90+
auto [ptr, error] = std::from_chars(str, str + strlen(str), value);
91+
if (error != std::errc())
92+
return std::nullopt;
93+
return value;
94+
}
95+
}

‎src/dash/DashStatistics.h

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#pragma once
2+
3+
#include "DashComponent.h"
4+
5+
namespace dash {
6+
// Statistic super class
7+
template <typename T = dash::string, uint8_t Precision = 2, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T> || std::is_same_v<T, dash::string> || std::is_same_v<T, const char*>, bool> = true>
8+
class Statistic : public Component {
9+
public:
10+
virtual ~Statistic() = default;
11+
12+
// statistic value
13+
const T& value() const { return _value; }
14+
15+
virtual void toJson(const JsonObject& json, bool onlyChanges) const override {
16+
Component::toJson(json, onlyChanges);
17+
if (!onlyChanges || hasChanged(Property::VALUE))
18+
dash::toJsonValue<T, Precision>(json["v"].to<JsonVariant>(), _value);
19+
}
20+
21+
protected:
22+
// construct a new statistic and add it to the dashboard
23+
Statistic(ESPDash& dashboard, const char* name, Type type) : Component(dashboard, name, type) {}
24+
// construct a new statistic without adding it to any dashboard
25+
Statistic(const char* name, Type type) : Component(name, type) {}
26+
27+
T _value;
28+
};
29+
30+
// Statistic value class with set method
31+
template <typename T = dash::string, uint8_t Precision = 2, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T> || std::is_same_v<T, dash::string> || std::is_same_v<T, const char*>, bool> = true>
32+
class StatisticValue : public Statistic<T, Precision> {
33+
public:
34+
// construct a new statistic and add it to the dashboard
35+
StatisticValue(ESPDash& dashboard, const char* name) : Statistic<T, Precision>(dashboard, name, Component::Type::STATISTIC_VALUE) {}
36+
// construct a new statistic without adding it to any dashboard
37+
StatisticValue(const char* name) : Statistic<T, Precision>(name, Component::Type::STATISTIC_VALUE) {}
38+
39+
~StatisticValue() = default;
40+
41+
// update the statistic value and returns true if the value has changed
42+
bool setValue(const T& value) {
43+
if (Statistic<T, Precision>::_value == value)
44+
return false;
45+
Statistic<T, Precision>::_value = value;
46+
Component::setChange(Component::Property::VALUE);
47+
return true;
48+
}
49+
50+
// update the statistic value and returns true if the value has changed
51+
bool setValue(T&& value) {
52+
if (Statistic<T, Precision>::_value == value)
53+
return false;
54+
Statistic<T, Precision>::_value = std::forward<T>(value);
55+
Component::setChange(Component::Property::VALUE);
56+
return true;
57+
}
58+
};
59+
60+
// Statistic provider class where the value if provided by a function
61+
template <typename T = dash::string, uint8_t Precision = 2, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T> || std::is_same_v<T, dash::string> || std::is_same_v<T, const char*>, bool> = true>
62+
class StatisticProvider : public Statistic<T, Precision> {
63+
private:
64+
std::function<T()> _provider = nullptr;
65+
66+
public:
67+
// construct a new statistic and add it to the dashboard
68+
StatisticProvider(ESPDash& dashboard, const char* name) : Statistic<T, Precision>(dashboard, name, Component::Type::STATISTIC_PROVIDER) {}
69+
// construct a new statistic without adding it to any dashboard
70+
StatisticProvider(const char* name) : Statistic<T, Precision>(name, Component::Type::STATISTIC_PROVIDER) {}
71+
72+
~StatisticProvider() = default;
73+
74+
// register a provider from where to get the statistic value
75+
void setProvider(std::function<T()> provider) { _provider = provider; }
76+
77+
// update the statistic value by calling the provider and returns true if the value has changed
78+
virtual bool selfUpdate() override {
79+
if (!_provider)
80+
return false;
81+
T value = _provider();
82+
if (Statistic<T, Precision>::_value == value)
83+
return false;
84+
Statistic<T, Precision>::_value = std::forward<T>(value);
85+
Component::setChange(Component::Property::VALUE);
86+
return true;
87+
}
88+
};
89+
} // namespace dash

‎src/dash/DashWidget.h

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#pragma once
2+
3+
#include "DashComponent.h"
4+
5+
namespace dash {
6+
class Widget : public Component {
7+
public:
8+
Widget(const char* name, Type type) : Component(name, type) {}
9+
Widget(ESPDash& dashboard, const char* name, Type type) : Component(dashboard, name, type) {}
10+
11+
virtual ~Widget() {}
12+
13+
// widget index (position) in the dashboard
14+
uint8_t index() const { return _index; }
15+
16+
// set the widget position in the dashboard
17+
bool setIndex(uint8_t index) {
18+
if (_index == index)
19+
return false;
20+
_index = index;
21+
setChange(Property::INDEX);
22+
return true;
23+
}
24+
25+
virtual void toJson(const JsonObject& json, bool onlyChanges) const override {
26+
Component::toJson(json, onlyChanges);
27+
if (!onlyChanges || hasChanged(Property::INDEX))
28+
json["t"] = _typeName(type());
29+
if (!onlyChanges || hasChanged(Property::INDEX))
30+
json["idx"] = _index;
31+
}
32+
33+
private:
34+
uint8_t _index;
35+
36+
// widget type name
37+
static const char* _typeName(Type type) {
38+
switch (type) {
39+
case Type::CARD_BUTTON:
40+
return "button";
41+
case Type::CARD_GENERIC:
42+
return "generic";
43+
case Type::CARD_HUMIDITY:
44+
return "humidity";
45+
case Type::CARD_PROGRESS:
46+
return "progress";
47+
case Type::CARD_SLIDER:
48+
return "slider";
49+
case Type::CARD_STATUS:
50+
return "status";
51+
case Type::CARD_TEMPERATURE:
52+
return "temperature";
53+
case Type::CHART_BAR:
54+
return "bar";
55+
default:
56+
assert(false);
57+
return "";
58+
}
59+
}
60+
};
61+
} // namespace dash

‎v5.md

+232
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
# ESP-DASH v5
2+
3+
ESP-DASH v5 is a rewrite of ESP-DASH \*\*OSS and Pro## Motivation versions with C++ 17.
4+
5+
## Motivation
6+
7+
The rewrite has been motived by several factors including:
8+
9+
- the inherent design inefficient of memory usage: widgets are using about 2.5 more RAN memory than they really need
10+
- websocket message optimisation: there are still room to allow more efficient message exchange between the client and the server
11+
- inability to extend the components and behavior
12+
- inability to correctly handle a custom float precision
13+
- inability to support all integral and floating point types
14+
- no namespace isolation
15+
- no STL string support
16+
- pointer vs ref: the old API uses pointers to widgets, which is not idiomatic C++ and can lead to bugs if null is passed.
17+
18+
The rewrite uses **C++ 17 inheritance, polymorphism and templating**.
19+
20+
The rewrite is also **backward compatible** and will display **deprecation compiler warnings** if the old deprecated API is used.
21+
22+
Here is a screenshot of before / after during the development.
23+
Now, the `website.cpp` file is 15KB, compared to the initial 36KB.
24+
25+
**The preliminary results in a big app with about 225 cards and 30 stats show a decrease in RAM usage of about 60%.**
26+
27+
![Screenshot 2024-11-29 at 10 07 58](https://github.com/user-attachments/assets/6c91fe35-9527-45d3-8cfb-2287690bbe72)
28+
29+
## Benchmark5.ino Example
30+
31+
[Benchmark5.ino](./examples/Benchmark5/Benchmark5.ino) is a new example that demonstrates the new API and the new features.
32+
33+
## Compile flags
34+
35+
C++ 17 is required, which is the default in new Arduino versions, otherwise, you can add to your PIO file:
36+
37+
```
38+
build_flags =
39+
-std=c++17
40+
-std=gnu++17
41+
-Wall -Wextra
42+
; -D DASH_USE_LEGACY_CHART_STORAGE=1
43+
; -D DASH_USE_STL_STRING=1
44+
; -D DASH_DEBUG
45+
build_unflags =
46+
-std=gnu++11
47+
```
48+
49+
- `-D DASH_DEBUG`: activate debug mode
50+
- `-D DASH_USE_STL_STRING=1`: uses `std::string` instead of `String` for the string type
51+
52+
ESP-DASH also defines its own string `dash::string`, which points to `String` or `std::string` depending on the flag `-D DASH_USE_STL_STRING=1`.
53+
`dash::string` can be used to avoid using `String` or `std::string` directly and write portable code or examples.
54+
55+
## API Changes
56+
57+
![image](https://github.com/user-attachments/assets/006f3ef1-7d01-4231-8ac7-4cc98df2e63d)
58+
59+
### All Widgets
60+
61+
- Widgets generally support templated types: integral, floating point, string (`const char*`, `std::string`, `String`)
62+
- Widgets generally support a custom precision for floating point types (default is 2)
63+
64+
Note: working with floating point numbers is generally slower than working with integral numbers because of the rounding step requiring to convert the number to a string representation with a fixed number of decimals.
65+
66+
### Statistics
67+
68+
- `dash::StatisticValue`: replaces `Statistic`
69+
- `dash::StatisticProvider`: a new kind of auto-updatable statistic: the value is sourced from a function and is automatically updated when the dashboard is refreshed.
70+
71+
```cpp
72+
// a string based statistic
73+
dash::StatisticValue stat(dashboard, "Client name");
74+
75+
// a statistic string based using a constant string pointer
76+
dash::StatisticValue<const char*> stat(dashboard, "State (on/off)");
77+
78+
// a float based statistic with a default precision of 2 decimals
79+
dash::StatisticValue<float> stat(dashboard, "Temperature (°C)");
80+
81+
// a float based statistic with a custom precision of 3 decimals
82+
dash::StatisticValue<float, 3> stat(dashboard, "Energy (kWh)");
83+
84+
// an integral based statistic
85+
dash::StatisticValue<int8_t> stat(dashboard, "Percent (%)");
86+
87+
dash::StatisticProvider<uint32_t> statProvider(dashboard, "Uptime (ms)");
88+
statProvider.setProvider([]() { return millis(); });
89+
```
90+
91+
### Charts
92+
93+
Charts have 2 axis: X and Y.
94+
For each axis, the type can be integral or floating point.
95+
For the X axis, strings are also supported.
96+
97+
```cpp
98+
dash::BarChart<const char*, int> bar(dashboard, "Power Usage (kWh) per day");
99+
100+
dash::BarChart<int, float> bar(dashboard, "Power Usage (kWh) per hour");
101+
```
102+
103+
**For performance reasons, floating point precision is not supported for charts.**
104+
It is advised to do the rounding in the value arrays.
105+
106+
### Cards
107+
108+
- `dash::FeedbackCard`
109+
110+
- Replaces `STATUS_CARD`
111+
- Defaults to `dash::string` type
112+
- Supports also `const char*` with `dash::FeedbackCard<const char*>`
113+
- Supports an initial state
114+
115+
```cpp
116+
dash::FeedbackCard<const char*> feedback(dashboard, "Status");
117+
feedback.setFeedback("Light is ON", dash::Status::SUCCESS);
118+
```
119+
120+
- `dash::GenericCard`
121+
- Replaces `GENERIC_CARD`
122+
- Defaults to `dash::string` type
123+
- Supports any integral, floating point type or string type
124+
- Supports a custom precision for floating point types
125+
- Supports a symbol or unit
126+
127+
```cpp
128+
dash::GenericCard<int> generic(dashboard, "Counter", "restarts");
129+
dash::GenericCard<float, 3> energy(dashboard, "Energy", "kWh");
130+
dash::GenericCard<float, 4> kp(dashboard, "PID Kp");
131+
```
132+
133+
- `dash::HumidityCard` and `dash::TemperatureCard`
134+
- Replaces `HUMIDITY_CARD` and `TEMPERATURE_CARD`
135+
- Defaults to `float` type with a precision of 2 decimals
136+
- Supports any integral or floating point type
137+
- Supports a custom precision for floating point types.
138+
- Unit is preset to `%` for humidity and `°C` for temperature but can be changed
139+
140+
```cpp
141+
dash::TemperatureCard<float, 1> temperature(dashboard, "Temperature");
142+
dash::TemperatureCard<float, 2> temperature(dashboard, "Temperature", "°F");
143+
```
144+
145+
- `dash::ProgressCard`
146+
147+
- Replaces `PROGRESS_CARD`
148+
- Defaults to `int` type
149+
- Supports any integral or floating point type
150+
- Supports a custom precision for floating point types.
151+
- Supports a symbol or unit
152+
- Allow a configurable min/max range
153+
154+
```cpp
155+
dash::ProgressCard<float, 2> preciseProgress(dashboard, "Progress", 0.0f, 100.0f, "%");
156+
```
157+
158+
- `dash::SliderCard`
159+
160+
- Replaces`SLIDER_CARD`
161+
- Defaults to `int` type
162+
- Supports any integral or floating point type
163+
- Supports a custom precision for floating point types.
164+
- Supports a symbol or unit
165+
- Allow a configurable min/max range
166+
- Allow a configurable step
167+
168+
```cpp
169+
dash::SliderCard duty(dashboard, "Duty", 0, 255, 1, "bits");
170+
dash::SliderCard<float, 3> kp(dashboard, "PID Kp", 0.0f, 1.0f, 0.001f);
171+
```
172+
173+
- `dash::SwitchCard`
174+
- Replaces `BUTTON_CARD`
175+
- Defaults to `bool` type
176+
177+
```cpp
178+
dash::SwitchCard light(dashboard, "Light");
179+
```
180+
181+
### Functions and callbacks
182+
183+
- `onChange([](<type>)){}`
184+
185+
Listen to card changes:
186+
187+
```cpp
188+
button.onChange([&](bool state) {
189+
/* Print our new button value received from dashboard */
190+
Serial.println(String("Button Triggered: ") + (state ? "true" : "false"));
191+
/* Make sure we update our button's value and send update to dashboard */
192+
button.setValue(state);
193+
dashboard.refresh(button);
194+
});
195+
196+
updateDelay.onChange([&](uint32_t value) {
197+
update_delay = value;
198+
updateDelay.setValue(value);
199+
dashboard.refresh(updateDelay);
200+
});
201+
```
202+
203+
- `value()`: get the value of a card
204+
- `min()`, `max()`, `step()`: get the min, max, step of a slider card
205+
- `setMin()`, `setMax()`, `setStep()`: set the min, max, step of a slider card
206+
- `setFeedback()`: set the feedback of a feedback card
207+
- `setValue()`: set the value of a card
208+
- etc
209+
210+
## Optimisations
211+
212+
By default, the string type that will be used to store string values is `String` or `std::string` if the flag `-D DASH_USE_STL_STRING=1` is set.
213+
214+
To avoid allocating memory and copying strings, the `const char*` type can be used when the card is sourcing its content from constant strings only.
215+
216+
**Example:**
217+
218+
```cpp
219+
dash::FeedbackCard<const char*> feedback(dashboard, "Status"); // uses c string pointers
220+
dash::FeedbackCard customFeedback(dashboard, "Status"); // uses String or std::string object
221+
222+
// [...]
223+
224+
if(lightON)
225+
feedback.setFeedback("Light is ON", dash::Status::SUCCESS);
226+
else
227+
feedback.setFeedback("Light is OFF", dash::Status::ERROR);
228+
229+
// [...]
230+
231+
customFeedback.setFeedback(dash::string("Counter: ") + count , dash::Status::WARNING);
232+
```

0 commit comments

Comments
 (0)
Please sign in to comment.