3#include <Adafruit_GFX.h>
4#include <Fonts/FreeSans12pt7b.h>
5#include <Adafruit_ST7735.h>
6#include <Adafruit_SSD1306.h>
26 DisplayType displayType;
32 uint8_t csPin, dcPin, rstPin;
37 Adafruit_ST7735 *pDisplayST;
38 Adafruit_SSD1306 *pDisplaySSD;
41 GfxDrivers(String name, DisplayType displayType, uint16_t resX, uint16_t resY, uint8_t i2cAddress, TwoWire *pWire = &Wire)
42 : name(name), displayType(displayType), resX(resX), resY(resY), i2cAddress(i2cAddress), pWire(pWire) {
44 pDisplaySSD =
nullptr;
47 if (displayType == DisplayType::SSD1306) {
49 busType = BusType::I2CBUS;
50 bgColor = SSD1306_BLACK;
56 GfxDrivers(String name, DisplayType displayType, uint16_t resX, uint16_t resY, uint8_t csPin, uint8_t dcPin, uint8_t rstPin = -1)
57 : name(name), displayType(displayType), resX(resX), resY(resY), csPin(csPin), dcPin(dcPin), rstPin(rstPin) {
59 pDisplaySSD =
nullptr;
62 if (displayType == DisplayType::ST7735) {
64 busType = BusType::SPIBUS;
65 bgColor = RGB(0, 0, 0);
74 void begin(
bool _useCanvas =
false) {
81 Serial.println(
"ERROR GfxDrivers::begin() - already begun");
86 useCanvas = _useCanvas;
88 switch (displayType) {
89 case DisplayType::SSD1306:
90 pDisplaySSD =
new Adafruit_SSD1306(resX, resY, pWire);
91 pDisplaySSD->begin(SSD1306_SWITCHCAPVCC, i2cAddress);
92 pDisplaySSD->clearDisplay();
93 pDisplaySSD->setTextWrap(
false);
94 pDisplaySSD->setTextColor(SSD1306_WHITE);
95 pDisplaySSD->cp437(
true);
97 case DisplayType::ST7735:
99 if (resX == 128 && resY == 128) {
100 pDisplayST =
new Adafruit_ST7735(csPin, dcPin, rstPin);
101 pDisplayST->initR(INITR_144GREENTAB);
102 }
else if (resX == 128 && resY == 160) {
103 pDisplayST =
new Adafruit_ST7735(csPin, dcPin, rstPin);
104 pDisplayST->initR(INITR_BLACKTAB);
107 Serial.println(
"ERROR GfxDrivers::begin() - unknown/invalid display resolution");
113 pCanvas =
new GFXcanvas16(resX, resY);
114 pCanvas->setTextWrap(
false);
115 pCanvas->fillScreen(ST77XX_BLACK);
116 pCanvas->cp437(
true);
118 pDisplayST->setTextWrap(
false);
119 pDisplayST->fillScreen(ST77XX_BLACK);
120 pDisplayST->cp437(
true);
127 void setBGColor(uint32_t _bgColor) {
129 splitRGB(_bgColor, &r, &g, &b);
130 if (r + b + g > 256 + 128) {
133 isLightTheme =
false;
138 static uint32_t RGB(uint8_t red, uint8_t green, uint8_t blue) {
139 uint32_t rgb = (((uint32_t)red) << 16) + (((uint32_t)green) << 8) + ((uint32_t)blue);
143 static void splitRGB(uint32_t rgb, uint8_t *pRed, uint8_t *pGreen, uint8_t *pBlue) {
144 *pRed = (uint8_t)((rgb >> 16) & 0xff);
145 *pGreen = (uint8_t)((rgb >> 8) & 0xff);
146 *pBlue = (uint8_t)(rgb & 0xff);
149 uint16_t rgbColor(uint8_t red, uint8_t green, uint8_t blue) {
150 switch (displayType) {
151 case DisplayType::SSD1306:
152 if (RGB(red, green, blue) == bgColor) {
154 return SSD1306_WHITE;
156 return SSD1306_BLACK;
159 return SSD1306_BLACK;
161 return SSD1306_WHITE;
163 case DisplayType::ST7735:
164 return (((red & 0xf8) << 8) + ((green & 0xfc) << 3) + (blue >> 3));
170 uint16_t rgbColor(uint32_t rgb) {
171 uint8_t red, green, blue;
172 splitRGB(rgb, &red, &green, &blue);
173 return rgbColor(red, green, blue);
176 void clearDisplay(uint32_t bgColor) {
178 switch (displayType) {
179 case DisplayType::SSD1306:
180 pDisplaySSD->clearDisplay();
181 pDisplaySSD->fillRect(0, 0, resX, resY, bgColor);
183 case DisplayType::ST7735:
185 pCanvas->fillScreen(bgColor);
188 pDisplayST->fillScreen(bgColor);
198 void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t rgb) {
202 Serial.println(
"ERROR GfxDrivers::drawLine() - not begun");
206 switch (displayType) {
207 case DisplayType::SSD1306:
208 pDisplaySSD->drawLine(x0, y0, x1, y1, rgbColor(rgb));
210 case DisplayType::ST7735:
211 if (useCanvas && pCanvas) {
213 pCanvas->drawFastHLine(x0, y0, x1 - x0, rgbColor(rgb));
215 pCanvas->drawLine(x0, y0, x1, y1, rgbColor(rgb));
219 pDisplayST->drawLine(x0, y0, x1, y1, rgbColor(rgb));
229 void fillRect(uint16_t x0, uint16_t y0, uint16_t lx, uint16_t ly, uint32_t rgb) {
231 switch (displayType) {
232 case DisplayType::SSD1306:
233 pDisplaySSD->fillRect(x0, y0, lx, ly, rgbColor(rgb));
235 case DisplayType::ST7735:
237 pCanvas->fillRect(x0, y0, lx, ly, rgbColor(rgb));
239 pDisplayST->fillRect(x0, y0, lx, ly, rgbColor(rgb));
248 void setFont(
const GFXfont *gfxFont = NULL) {
250 switch (displayType) {
251 case DisplayType::SSD1306:
252 pDisplaySSD->setFont(gfxFont);
254 case DisplayType::ST7735:
256 pCanvas->setFont(gfxFont);
258 pDisplayST->setFont(gfxFont);
267 void setTextColor(uint32_t rgb) {
269 switch (displayType) {
270 case DisplayType::SSD1306:
271 pDisplaySSD->setTextColor(rgbColor(rgb));
273 case DisplayType::ST7735:
275 pCanvas->setTextColor(rgbColor(rgb));
277 pDisplayST->setTextColor(rgbColor(rgb));
286 void setTextSize(uint16_t textSize) {
288 switch (displayType) {
289 case DisplayType::SSD1306:
290 pDisplaySSD->setTextSize(textSize);
292 case DisplayType::ST7735:
294 pCanvas->setTextSize(textSize);
296 pDisplayST->setTextSize(textSize);
305 void setCursor(uint16_t x, uint16_t y) {
307 switch (displayType) {
308 case DisplayType::SSD1306:
309 pDisplaySSD->setCursor(x, y);
311 case DisplayType::ST7735:
313 pCanvas->setCursor(x, y);
315 pDisplayST->setCursor(x, y);
324 void println(String &text) {
326 switch (displayType) {
327 case DisplayType::SSD1306:
328 pDisplaySSD->println(text);
330 case DisplayType::ST7735:
332 pCanvas->println(text);
334 pDisplayST->println(text);
345 switch (displayType) {
346 case DisplayType::SSD1306:
347 pDisplaySSD->display();
349 case DisplayType::ST7735:
351 pDisplayST->drawRGBBitmap(0, 0, pCanvas->getBuffer(), resX, resY);
466 GfxDrivers::DisplayType displayType;
468 uint16_t slotResX, slotResY;
471 uint8_t csPin, dcPin, rstPin;
475 GfxDrivers *pDisplay;
476 ustd::Scheduler *pSched;
477#if defined(USTD_FEATURE_NETWORK) && !defined(OPTION_NO_MQTT)
481 enum SlotType { NUMBER,
484 uint32_t defaultColor;
485 uint32_t defaultBgColor;
486 uint32_t defaultSeparatorColor;
487 uint32_t defaultAccentColor;
488 uint32_t defaultIncreaseColor;
489 uint32_t defaultConstColor;
490 uint32_t defaultDecreaseColor;
491 uint16_t defaultHistLen;
492 uint32_t defaultHistSampleRateMs;
493 typedef struct t_slot {
511 uint32_t lastHistUpdate;
512 uint32_t histSampleRateMs;
530 ustd::array<String> topics;
531 ustd::array<String> captions;
532 ustd::array<String> msgs;
534 uint32_t displayFrameRateMs;
535 uint32_t lastRefresh;
536 uint32_t minUpdateIntervalMs;
538 String valid_formats =
" SIPFDTG";
539 String valid_formats_long =
" SIPFDTG";
540 String valid_formats_small =
" sipfdtg";
541 char oldTimeString[64] =
"";
543 GfxPanel(String name, GfxDrivers::DisplayType displayType, uint16_t resX, uint16_t resY, uint8_t i2cAddress, TwoWire *pWire = &Wire, String locale =
"C")
544 : name(name), displayType(displayType), resX(resX), resY(resY), i2cAddress(i2cAddress), pWire(pWire), locale(locale)
555 switch (displayType) {
556 case GfxDrivers::DisplayType::SSD1306:
557 pDisplay =
new GfxDrivers(name, displayType, resX, resY, i2cAddress, pWire);
565 GfxPanel(String name, GfxDrivers::DisplayType displayType, uint16_t resX, uint16_t resY, uint8_t csPin, uint8_t dcPin, uint8_t rstPin = -1, String locale =
"C")
566 : name(name), displayType(displayType), resX(resX), resY(resY), csPin(csPin), dcPin(dcPin), rstPin(rstPin), locale(locale)
578 switch (displayType) {
579 case GfxDrivers::DisplayType::ST7735:
580 pDisplay =
new GfxDrivers(name, displayType, resX, resY, csPin, dcPin, rstPin);
593 for (uint16_t i = 0; i < slots; i++) {
594 if (pSlots[i].pHist) {
595 delete pSlots[i].pHist;
605 uint32_t relRGB(uint8_t r, uint8_t g, uint8_t b,
float brightness,
float contrast) {
607 rt = (int16_t)((((
float)r - 128.0f) * contrast * 2.0f + 128.0f) * brightness * 2.0f);
608 if (rt > (int16_t)0xff) rt = 0xff;
609 if (rt < (int16_t)0) rt = 0;
610 gt = (int16_t)((((
float)g - 128.0f) * contrast * 2.0f + 128.0f) * brightness * 2.0f);
611 if (gt > (int16_t)0xff) gt = 0xff;
612 if (gt < (int16_t)0) gt = 0;
613 bt = (int16_t)((((
float)b - 128.0f) * contrast * 2.0f + 128.0f) * brightness * 2.0f);
614 if (bt > (int16_t)0xff) bt = 0xff;
615 if (bt < (int16_t)0) bt = 0;
616 return GfxDrivers::RGB((uint8_t)rt, (uint8_t)gt, (uint8_t)bt);
620 float brightness, contrast;
621 enum Theme { ThemeDark,
625 ThemeSolarizedLight };
627 void _setTheme(Theme theme) {
628 themeType = Theme::ThemeDark;
633 defaultColor = relRGB(0x00, 0x00, 0x00, brightness, contrast);
634 defaultBgColor = relRGB(0xff, 0xff, 0xff, brightness, contrast);
635 defaultSeparatorColor = relRGB(0x80, 0x80, 0x80, brightness, contrast);
636 defaultAccentColor = relRGB(0x40, 0x40, 0x40, brightness, contrast);
637 defaultIncreaseColor = relRGB(0xff, 0xa0, 0xa0, brightness, contrast);
638 defaultConstColor = relRGB(0x30, 0x30, 0x30, brightness, contrast);
639 defaultDecreaseColor = relRGB(0xa0, 0xa0, 0xff, brightness, contrast);
641 case ThemeSolarizedLight:
642 themeName =
"solarizedlight";
644 defaultColor = relRGB(0x00, 0x2b, 0x36, brightness, contrast);
645 defaultBgColor = relRGB(0xee, 0xe8, 0x95, brightness, contrast);
646 defaultSeparatorColor = relRGB(0x58, 0x6e, 0x05, brightness, contrast);
647 defaultAccentColor = relRGB(0x67, 0x76, 0x02, brightness, contrast);
648 defaultIncreaseColor = relRGB(0xeb, 0x4b, 0x16, brightness, contrast);
649 defaultConstColor = relRGB(0x50, 0x50, 0x30, brightness, contrast);
650 defaultDecreaseColor = relRGB(0x43, 0x64, 0xe6, brightness, contrast);
655 themeType = Theme::ThemeDark;
656 defaultColor = relRGB(0xff, 0xff, 0xff, brightness, contrast);
657 defaultBgColor = relRGB(0x00, 0x00, 0x00, brightness, contrast);
658 defaultSeparatorColor = relRGB(0x80, 0x80, 0x80, brightness, contrast);
659 defaultAccentColor = relRGB(0xb0, 0xb0, 0xb0, brightness, contrast);
660 defaultIncreaseColor = relRGB(0xff, 0x80, 0x80, brightness, contrast);
661 defaultConstColor = relRGB(0xc0, 0xc0, 0xc0, brightness, contrast);
662 defaultDecreaseColor = relRGB(0x80, 0x80, 0xff, brightness, contrast);
665 for (uint16_t slot = 0; slot < slots; slot++) {
666 pSlots[slot].color = defaultColor;
667 pSlots[slot].bgColor = defaultBgColor;
669 pDisplay->setBGColor(defaultBgColor);
671 Serial.println(
"setTheme: " + themeName);
675 void _common_init() {
677 displayFrameRateMs = 1000;
682#if USTD_FEATURE_MEMORY >= USTD_FEATURE_MEM_128K
683 defaultHistLen = 128;
684#elif USTD_FEATURE_MEMORY >= USTD_FEATURE_MEM_32K
689 defaultHistSampleRateMs = 3600 * 1000 / 64;
692 bool splitCombinedLayout(String combined_layout) {
693 bool layout_valid =
true;
702 int ind = combined_layout.indexOf(
'|');
704 line = combined_layout;
705 combined_layout =
"";
707 line = combined_layout.substring(0, ind);
708 combined_layout = combined_layout.substring(ind + 1);
710 for (
char c : line) {
711 if (valid_formats_small.indexOf(c) == -1 && valid_formats_long.indexOf(c) == -1) {
712 layout_valid =
false;
716 ind = valid_formats_small.indexOf(c);
718 c = valid_formats_long[ind];
727 if (!layout_valid)
break;
728 if (combined_layout !=
"") {
739 bool getConfigFromFS(String name) {
744 String combined_layout = jf.readString(name +
"/layout",
"ff|ff");
745 if (!splitCombinedLayout(combined_layout)) {
748 for (uint8_t i = 0; i < slots; i++) {
749 captions[i] =
"room";
750 topics[i] =
"some/topic";
752 jf.readStringArray(name +
"/topics", topics);
753 jf.readStringArray(name +
"/captions", captions);
754 if (topics.length() != captions.length() || topics.length() != slots) {
756 Serial.println(
"Error: topics, captions and layout do not match");
763 bool getConfigFromLayout(String name, String combined_layout) {
769 if (!splitCombinedLayout(combined_layout)) {
772 if (topics.length() != captions.length() || topics.length() != slots) {
774 Serial.println(
"Error: topics, captions and layout do not match");
781 bool config2slot(uint16_t slot) {
789 pSlots[slot].slotX = 0;
790 pSlots[slot].slotY = 0;
792 for (
auto c : layout) {
795 pSlots[slot].slotLenX = 1;
796 pSlots[slot].slotLenY = 1;
799 pSlots[slot].slotX++;
802 }
else if (c ==
'L') {
804 pSlots[slot].slotLenX = 2;
805 pSlots[slot].slotLenY = 1;
808 pSlots[slot].slotX += 2;
811 }
else if (c ==
'|') {
812 pSlots[slot].slotY++;
813 pSlots[slot].slotX = 0;
818 pSlots[slot].histLen = defaultHistLen;
819 pSlots[slot].offset = 0.0;
820 pSlots[slot].scalingFactor = 1.0;
821 switch (formats[slot]) {
823 pSlots[slot].slotType = SlotType::NUMBER;
824 pSlots[slot].digits = 0;
827 pSlots[slot].slotType = SlotType::NUMBER;
828 pSlots[slot].digits = 1;
831 pSlots[slot].slotType = SlotType::NUMBER;
832 pSlots[slot].digits = 2;
835 pSlots[slot].slotType = SlotType::NUMBER;
836 pSlots[slot].digits = 3;
839 pSlots[slot].slotType = SlotType::TEXT;
840 pSlots[slot].digits = 3;
841 pSlots[slot].histLen = 0;
844 pSlots[slot].slotType = SlotType::NUMBER;
845 pSlots[slot].scalingFactor = 100.0;
846 pSlots[slot].digits = 1;
849 pSlots[slot].slotType = SlotType::GRAPH;
850 pSlots[slot].digits = 3;
855 if (pSlots[slot].histLen) {
856 pSlots[slot].pHist = (
float *)malloc(
sizeof(
float) * pSlots[slot].histLen);
857 pSlots[slot].histInit =
false;
858 if (pSlots[slot].pHist) {
859 for (uint16_t j = 0; j < pSlots[slot].histLen; j++) {
860 pSlots[slot].pHist[j] = 0;
863 pSlots[slot].histLen = 0;
867 pSlots[slot].pHist =
nullptr;
868 pSlots[slot].histLen = 0;
870 pSlots[slot].topic = topics[slot];
871 pSlots[slot].caption = captions[slot];
872 pSlots[slot].lastUpdate = time(
nullptr);
873 pSlots[slot].lastHistUpdate = 0;
874 pSlots[slot].histSampleRateMs = defaultHistSampleRateMs;
875 pSlots[slot].currentValue = 0.0;
876 pSlots[slot].currentText =
"";
877 pSlots[slot].deltaDir = 0.0;
878 pSlots[slot].isInit =
true;
879 pSlots[slot].isValid =
false;
880 pSlots[slot].color = defaultColor;
881 pSlots[slot].bgColor = defaultBgColor;
882 pSlots[slot].lastFrame = 0;
883 pSlots[slot].frameRate = 1000;
887 bool shortConfig2Slots() {
891 if (formats.length() != slots) {
893 Serial.println(
"Error: formats and slots number do not match");
897 slots = formats.length();
898 pSlots =
new T_SLOT[slots];
899 for (uint16_t i = 0; i < slots; i++) {
900 if (!config2slot(i)) {
908 const char *weekDays[] = {
"Su",
"Mo",
"Tu",
"We",
"Th",
"Fr",
"Sa"};
909 const char *wochenTage[] = {
"So",
"Mo",
"Di",
"Mi",
"Do",
"Fr",
"Sa"};
919 pDay = wochenTage[plt->tm_wday];
921 pDay = weekDays[plt->tm_wday];
923 sprintf(buf,
"%s %02d:%02d", pDay, plt->tm_hour, plt->tm_min);
924 if (strcmp(buf, oldTimeString)) {
925 strcpy(oldTimeString, buf);
926 sensorUpdates(
"clock/timeinfo", String(buf),
"self.local");
929 for (uint8_t i = 0; i < slots; i++) {
930 if (time(
nullptr) - pSlots[i].lastUpdate > 3600) {
931 pSlots[i].isValid =
false;
933 if (pSlots[i].slotType == SlotType::GRAPH) {
934 if (!pSlots[i].hasChanged) {
935 updateSlot(i, pSlots[i].currentText);
939 updateDisplay(
false,
false);
942 void commonBegin(
bool useCanvas =
false) {
943 pDisplay->begin(useCanvas);
948 minUpdateIntervalMs = 50;
949 int tID = pSched->add(fntsk, name, minUpdateIntervalMs * 1000L);
950 auto fnsub = [=](String topic, String msg, String originator) {
951 this->subsMsg(topic, msg, originator);
953 pSched->subscribe(tID, name +
"/display/#", fnsub);
954 auto fnall = [=](String topic, String msg, String originator) {
955 sensorUpdates(topic, msg, originator);
957 for (uint8_t i = 0; i < slots; i++) {
958 if (topics[i] !=
"") {
959#if defined(USTD_FEATURE_NETWORK) && !defined(OPTION_NO_MQTT)
960 if (topics[i][0] ==
'!') {
961 topics[i] = topics[i].substring(1);
962 pMqtt->addSubscription(tID, topics[i], fnall);
964 Serial.print(
"Subscribing via MQTT: ");
965 Serial.println(topics[i]);
969 if (topics[i][0] !=
'!') {
970 if (topics[i] !=
"clock/timeinfo") {
971 pSched->subscribe(tID, topics[i], fnall);
973 Serial.print(
"Subscribing internally: ");
974 Serial.println(topics[i]);
978 Serial.print(
"Internal topic: ");
979 Serial.println(topics[i]);
988 Serial.print(
"Layout: ");
989 Serial.print(layout);
990 Serial.print(
" formats: ");
991 Serial.print(formats);
992 Serial.print(
" histLen: ");
993 Serial.println(defaultHistLen);
997 _setTheme(Theme::ThemeDark);
998 updateDisplay(
true,
true);
1002 void setBrightness(
float _brightness = 0.5) {
1003 if (!active)
return;
1004 if (_brightness < 0.0) _brightness = 0.0;
1005 if (_brightness > 1.0) _brightness = 1.0;
1006 brightness = _brightness;
1007 _setTheme(themeType);
1008 updateDisplay(
true,
true);
1011 void publishBrightness() {
1012 if (!active)
return;
1013 pSched->publish(name +
"/display/brightness", String(brightness, 3));
1016 void setContrast(
float _contrast = 0.5) {
1017 if (!active)
return;
1018 if (_contrast < 0.0) _contrast = 0.0;
1019 if (_contrast > 1.0) _contrast = 1.0;
1020 contrast = _contrast;
1021 _setTheme(themeType);
1022 updateDisplay(
true,
true);
1025 void publishContrast() {
1026 if (!active)
return;
1027 pSched->publish(name +
"/display/contrast", String(contrast, 3));
1030 void setTheme(String _theme) {
1031 if (!active)
return;
1035 if (_theme ==
"light") {
1036 _setTheme(Theme::ThemeLight);
1039 if (_theme ==
"solarizedlight") {
1040 _setTheme(Theme::ThemeSolarizedLight);
1044 _setTheme(Theme::ThemeDark);
1046 updateDisplay(
true,
true);
1049 void publishTheme() {
1050 if (!active)
return;
1051 pSched->publish(name +
"/display/theme", themeName);
1059 if (!active)
return;
1060 captions[slot] = caption;
1061 if (pSlots && slot < slots) {
1062 if (pSlots[slot].isInit) {
1063 pSlots[slot].caption = caption;
1064 pSlots[slot].hasChanged =
true;
1067 updateDisplay(
false,
false);
1074 if (!active)
return;
1079 if (pSlots[slot].isInit) {
1080 cap = pSlots[slot].caption;
1085 cap = captions[slot];
1087 pSched->publish(name +
"/display/slot/" + String(slot) +
"/caption", cap);
1096 if (!active)
return;
1105 if (!active)
return;
1116 if (!active)
return;
1122 if (!active)
return;
1129 if (!active)
return;
1130 if (slot < slots && format.length() == 1) {
1131 formats[slot] = format[0];
1132 pSlots[slot].hasChanged =
true;
1133 updateDisplay(
true,
true);
1140 if (!active)
return;
1148 if (!active)
return;
1150 pSlots[slot].histSampleRateMs = rate;
1151 pSlots[slot].frameRate = rate;
1152 if (rate < displayFrameRateMs) {
1153 displayFrameRateMs = rate;
1162 if (!active)
return;
1164 pSched->publish(name +
"/display/slot/" + String(slot) +
"/histosrysampleratems", String(pSlots[slot].histSampleRateMs));
1167#if defined(USTD_FEATURE_NETWORK) && !defined(OPTION_NO_MQTT)
1168 void begin(ustd::Scheduler *_pSched, ustd::Mqtt *_pMqtt,
bool _useCanvas =
false) {
1178 void begin(ustd::Scheduler *_pSched,
bool _useCanvas =
false) {
1186 getConfigFromFS(name);
1187 commonBegin(_useCanvas);
1190#if defined(USTD_FEATURE_NETWORK) && !defined(OPTION_NO_MQTT)
1191 void begin(ustd::Scheduler *_pSched, ustd::Mqtt *_pMqtt, String combined_layout, ustd::array<String> _topics, ustd::array<String> _captions,
bool _useCanvas =
false) {
1204 void begin(ustd::Scheduler *_pSched, String combined_layout, ustd::array<String> _topics, ustd::array<String> _captions,
bool _useCanvas =
false) {
1215 for (
auto t : _topics) {
1218 for (
auto c : _captions) {
1222 getConfigFromLayout(name, combined_layout);
1223 commonBegin(_useCanvas);
1226#if defined(USTD_FEATURE_NETWORK) && !defined(OPTION_NO_MQTT)
1227 void begin(ustd::Scheduler *_pSched, ustd::Mqtt *_pMqtt, String combined_layout, uint16_t _slots,
const char *_topics[],
const char *_captions[],
bool _useCanvas =
false) {
1241 void begin(ustd::Scheduler *_pSched, String combined_layout, uint16_t _slots,
const char *_topics[],
const char *_captions[],
bool _useCanvas =
false) {
1253 for (uint16_t i = 0; i < _slots; i++) {
1254 String s = _topics[i];
1256 String c = _captions[i];
1259 getConfigFromLayout(name, combined_layout);
1260 commonBegin(_useCanvas);
1264 void drawArrow(uint16_t x, uint16_t y,
bool up =
true, uint16_t len = 8, uint16_t wid = 3, int16_t delta_down = 0) {
1265 uint32_t red = defaultIncreaseColor;
1266 uint32_t blue = defaultDecreaseColor;
1268 pDisplay->drawLine(x, y + len, x, y, red);
1269 pDisplay->drawLine(x + 1, y + len, x + 1, y, red);
1270 pDisplay->drawLine(x, y, x - wid, y + wid, red);
1271 pDisplay->drawLine(x, y, x + wid, y + wid, red);
1272 pDisplay->drawLine(x + 1, y, x - wid + 1, y + wid, red);
1273 pDisplay->drawLine(x + 1, y, x + wid + 1, y + wid, red);
1275 pDisplay->drawLine(x, y + len + delta_down, x, y + delta_down, blue);
1276 pDisplay->drawLine(x + 1, y + len + delta_down, x + 1, y + delta_down, blue);
1277 pDisplay->drawLine(x, y + len + delta_down, x - wid, y + len - wid + delta_down, blue);
1278 pDisplay->drawLine(x, y + len + delta_down, x + wid, y + len - wid + delta_down, blue);
1279 pDisplay->drawLine(x + 1, y + len + delta_down, x - wid + 1, y + len - wid + delta_down, blue);
1280 pDisplay->drawLine(x + 1, y + len + delta_down, x + wid + 1, y + len - wid + delta_down, blue);
1284 void boldParser(String msg, String &first, String &sec) {
1286 for (
unsigned int i = 0; i < msg.length(); i++) {
1287 if (msg[i] ==
'_') {
1301 bool displaySlot(uint16_t slot) {
1302 if (slot >= slots)
return false;
1304 uint8_t x0 = 0, y0 = 0, x1 = 0, y1 = 0, xa = 0, ya = 0;
1305 uint8_t xm0, ym0, xm1, ym1;
1308 uint16_t xf0, yf0, xl, yl;
1309 xf0 = pSlots[slot].slotX * slotResX;
1310 xl = slotResX * pSlots[slot].slotLenX;
1311 yf0 = pSlots[slot].slotY * slotResY + 1;
1312 yl = slotResY * pSlots[slot].slotLenY - 1;
1313 pDisplay->fillRect(xf0, yf0, xl, yl, pSlots[slot].bgColor);
1315 x0 = pSlots[slot].slotX * slotResX + 14;
1316 y0 = pSlots[slot].slotY * slotResY + 3;
1317 x1 = pSlots[slot].slotX * slotResX + 14;
1318 y1 = pSlots[slot].slotY * slotResY + slotResY - 3;
1319 xa = pSlots[slot].slotX * slotResX + 5;
1320 ya = pSlots[slot].slotY * slotResY + 14;
1321 xm0 = pSlots[slot].slotX * slotResX + 1;
1322 ym0 = pSlots[slot].slotY * slotResY + 1;
1323 xm1 = (pSlots[slot].slotX + 1) * slotResX - 2 + (pSlots[slot].slotLenX - 1) * slotResX;
1324 ym1 = (pSlots[slot].slotY + 1) * slotResY - 2 + (pSlots[slot].slotLenY - 1) * slotResY;
1327 pDisplay->setFont();
1328 pDisplay->setTextColor(defaultAccentColor);
1329 pDisplay->setTextSize(1);
1330 String first =
"", second =
"";
1331 boldParser(pSlots[slot].caption, first, second);
1332 pDisplay->setCursor(x0, y0);
1333 pDisplay->println(first);
1334 pDisplay->setCursor(x0 + 1, y0);
1335 pDisplay->println(second);
1337 float gmin, gmax, dmin, dmax;
1339 dmin = 1000000000.0;
1340 dmax = -1000000000.0;
1341 gmax = -1000000000.0;
1342 gmin = 1000000000.0;
1345 if (pSlots[slot].slotType != SlotType::TEXT) {
1346 if (pSlots[slot].pHist && pSlots[slot].histLen) {
1347 for (uint16_t x = 0; x < pSlots[slot].histLen; x++) {
1348 if (pSlots[slot].pHist[x] > gmax) gmax = pSlots[slot].pHist[x];
1349 if (pSlots[slot].pHist[x] < gmin) gmin = pSlots[slot].pHist[x];
1350 uint16_t backView = 0;
1351 if (pSlots[slot].histLen >= 10) backView = pSlots[slot].histLen - 10;
1352 if (x >= backView) {
1353 avg += pSlots[slot].pHist[x];
1355 if (pSlots[slot].pHist[x] > dmax) dmax = pSlots[slot].pHist[x];
1356 if (pSlots[slot].pHist[x] < dmin) dmin = pSlots[slot].pHist[x];
1364 pSlots[slot].deltaDir = pSlots[slot].currentValue - ref;
1367 if (pSlots[slot].slotType != SlotType::GRAPH) {
1369 pDisplay->setFont(&FreeSans12pt7b);
1370 pDisplay->setTextColor(defaultColor);
1371 pDisplay->setTextSize(1);
1372 pDisplay->setCursor(x1, y1);
1373 pDisplay->println(pSlots[slot].currentText);
1374 pDisplay->setCursor(x1 + 1, y1);
1375 pDisplay->println(pSlots[slot].currentText);
1376 if (pSlots[slot].slotType != SlotType::TEXT) {
1378 if (pSlots[slot].deltaDir != 0.0) {
1379 drawArrow(xa, ya, (pSlots[slot].deltaDir > 0.0), 8, 3, 7);
1384 if (pSlots[slot].pHist && pSlots[slot].histLen) {
1385 float deltaY = gmax - gmin;
1386 if (deltaY < 0.0001) deltaY = 1;
1387 float deltaX = (float)(xm1 - xm0) / (float)(pSlots[slot].histLen);
1388 int lx0, ly0, lx1, ly1;
1389 int gHeight = (ym1 - ym0) - 11;
1390 for (uint16_t i = 1; i < pSlots[slot].histLen; i++) {
1391 lx0 = xm0 + (int)((
float)(i - 1) * deltaX);
1392 lx1 = xm0 + (int)((
float)i * deltaX);
1393 ly0 = ym1 - (int)((pSlots[slot].pHist[i - 1] - gmin) / deltaY * (float)(gHeight));
1394 ly1 = ym1 - (int)((pSlots[slot].pHist[i] - gmin) / deltaY * (float)(gHeight));
1397 col = defaultIncreaseColor;
1400 col = defaultConstColor;
1402 col = defaultDecreaseColor;
1404 pDisplay->drawLine(lx0, ly0, lx1, ly1, col);
1408 pSlots[slot].hasChanged =
false;
1413 void updateDisplay(
bool updateNow,
bool forceRedraw =
false) {
1414 if (!updateNow && timeDiff(lastRefresh, millis()) < displayFrameRateMs) {
1417 bool update =
false;
1418 lastRefresh = millis();
1419 uint16_t maxSlotX = 0, maxSlotY = 0;
1422 pDisplay->clearDisplay(defaultBgColor);
1423 for (uint16_t slot = 0; slot < slots; slot++) {
1424 if (pSlots[slot].slotX > maxSlotX) maxSlotX = pSlots[slot].slotX;
1425 if (pSlots[slot].slotY > maxSlotY) maxSlotY = pSlots[slot].slotY;
1428 for (uint16_t ly = 0; ly <= maxSlotY + 1; ly++) {
1430 if (y >= resY) y = resY - 1;
1432 pDisplay->drawLine(0, y, resX - 1, y, defaultSeparatorColor);
1435 for (uint16_t slot = 0; slot < slots; slot++) {
1436 if (pSlots[slot].hasChanged || forceRedraw) {
1437 if (displaySlot(slot)) update =
true;
1440 if (update || forceRedraw) {
1441 pDisplay->display();
1446 bool updateSlot(uint16_t slot, String msg) {
1447 if (slot >= slots)
return false;
1448 if (timeDiff(pSlots[slot].lastFrame, millis()) < pSlots[slot].frameRate)
return false;
1449 bool changed =
false;
1450 float k = pSlots[slot].scalingFactor;
1451 float o = pSlots[slot].offset;
1452 switch (pSlots[slot].slotType) {
1453 case SlotType::TEXT:
1454 if (pSlots[slot].currentText != msg) {
1457 pSlots[slot].currentText = msg;
1458 pSlots[slot].isValid =
true;
1459 pSlots[slot].lastUpdate = time(
nullptr);
1460 pSlots[slot].lastFrame = millis();
1462 case SlotType::NUMBER:
1463 case SlotType::GRAPH:
1464 pSlots[slot].currentValue = msg.toFloat() * k + o;
1465 pSlots[slot].isValid =
true;
1466 pSlots[slot].lastUpdate = time(
nullptr);
1467 pSlots[slot].lastFrame = millis();
1468 String newVal = String(pSlots[slot].currentValue, (uint16_t)pSlots[slot].digits);
1469 if (pSlots[slot].currentText != newVal) {
1472 pSlots[slot].currentText = newVal;
1473 if (pSlots[slot].pHist && pSlots[slot].histLen > 0) {
1474 if (!pSlots[slot].histInit) {
1475 for (uint16_t i = 0; i < pSlots[slot].histLen; i++) {
1476 pSlots[slot].pHist[i] = pSlots[slot].currentValue;
1478 pSlots[slot].lastHistUpdate = millis();
1479 pSlots[slot].histInit =
true;
1482 while (timeDiff(pSlots[slot].lastHistUpdate, millis()) > pSlots[slot].histSampleRateMs) {
1483 for (uint16_t i = 0; i < pSlots[slot].histLen - 1; i++) {
1484 pSlots[slot].pHist[i] = pSlots[slot].pHist[i + 1];
1486 pSlots[slot].lastHistUpdate += pSlots[slot].histSampleRateMs;
1487 pSlots[slot].pHist[pSlots[slot].histLen - 1] = pSlots[slot].currentValue;
1488 if (pSlots[slot].slotType == SlotType::GRAPH) {
1492 pSlots[slot].pHist[pSlots[slot].histLen - 1] = pSlots[slot].currentValue;
1497 pSlots[slot].hasChanged = changed;
1501 void sensorUpdates(String topic, String msg, String originator) {
1502 if (!active)
return;
1503 bool changed =
false;
1504 for (uint16_t slot = 0; slot < slots; slot++) {
1505 if (pSlots[slot].topic == topic) {
1506 if (updateSlot(slot, msg)) changed =
true;
1509 if (changed) updateDisplay(
false,
false);
1512 void subsMsg(String topic, String msg, String originator) {
1513 if (!active)
return;
1514 String toc = name +
"/display/slot/";
1515 if (topic.startsWith(toc)) {
1516 String sub = topic.substring(toc.length());
1517 int16_t ind = sub.indexOf(
"/");
1519 int16_t slot = sub.substring(0, ind).toInt();
1521 String action = sub.substring(ind + 1);
1522 if (action ==
"caption/get") {
1524 }
else if (action ==
"caption/set") {
1526 }
else if (action ==
"format/get") {
1528 }
else if (action ==
"format/set") {
1530 }
else if (action ==
"topic/get") {
1532 }
else if (action ==
"topic/set") {
1534 }
else if (action ==
"text/get") {
1536 }
else if (action ==
"text/set") {
1538 }
else if (action ==
"historysampleratems/get") {
1540 }
else if (action ==
"historysampleratems/set") {
1545 }
else if (topic == name +
"/display/brightness/set") {
1546 float br = msg.toFloat();
1547 if (br < 0.0) br = 0.0;
1548 if (br > 1.0) br = 1.0;
1550 }
else if (topic == name +
"/display/brightness/get") {
1551 publishBrightness();
1552 }
else if (topic == name +
"/display/contrast/set") {
1553 float c = msg.toFloat();
1554 if (c < 0.0) c = 0.0;
1555 if (c > 1.0) c = 1.0;
1557 }
else if (topic == name +
"/display/contrast/get") {
1559 }
else if (topic == name +
"/display/theme/set") {
1561 }
else if (topic == name +
"/display/theme/get") {
gfx_panel mupplet for oled and tft panels to display sensor values
Definition: mup_gfx_panel.h:463
void setSlotFormat(uint16_t slot, String format)
Definition: mup_gfx_panel.h:1124
void publishSlotHistorySampleRateMs(uint16_t slot)
Definition: mup_gfx_panel.h:1158
void setSlotCaption(uint16_t slot, String caption)
Definition: mup_gfx_panel.h:1054
GfxPanel(String name, GfxDrivers::DisplayType displayType, uint16_t resX, uint16_t resY, uint8_t csPin, uint8_t dcPin, uint8_t rstPin=-1, String locale="C")
Definition: mup_gfx_panel.h:565
void begin(ustd::Scheduler *_pSched, bool _useCanvas=false)
Definition: mup_gfx_panel.h:1178
void begin(ustd::Scheduler *_pSched, String combined_layout, ustd::array< String > _topics, ustd::array< String > _captions, bool _useCanvas=false)
Definition: mup_gfx_panel.h:1204
void begin(ustd::Scheduler *_pSched, String combined_layout, uint16_t _slots, const char *_topics[], const char *_captions[], bool _useCanvas=false)
Definition: mup_gfx_panel.h:1241
void setSlotHistorySampleRateMs(uint16_t slot, uint32_t rate)
Definition: mup_gfx_panel.h:1143
void publishSlotText(uint16_t slot)
Definition: mup_gfx_panel.h:1101
void publishSlotFormat(uint16_t slot)
Definition: mup_gfx_panel.h:1136
GfxPanel(String name, GfxDrivers::DisplayType displayType, uint16_t resX, uint16_t resY, uint8_t i2cAddress, TwoWire *pWire=&Wire, String locale="C")
Definition: mup_gfx_panel.h:543
void setSlotTopic(uint16_t slot, String topic)
Definition: mup_gfx_panel.h:1111
void publishSlotTopic(uint16_t slot)
Definition: mup_gfx_panel.h:1118
void publishSlotCaption(uint16_t slot)
Definition: mup_gfx_panel.h:1070
void setSlotText(uint16_t slot, String text)
Definition: mup_gfx_panel.h:1091
The muwerk namespace.
Definition: display_digits_max72xx.h:10