muwerk mupplet Display Library
muwerk applets; mupplets: functional units that support specific hardware or reusable applications
Loading...
Searching...
No Matches
mup_gfx_panel.h
1#pragma once
2
3#include <Adafruit_GFX.h>
4#include <Fonts/FreeSans12pt7b.h>
5#include <Adafruit_ST7735.h>
6#include <Adafruit_SSD1306.h>
7#include "jsonfile.h"
8
9ustd::jsonfile jf;
10
11namespace ustd {
12
13class GfxDrivers {
14 public:
15 enum DisplayType {
16 SSD1306,
17 ST7735
18 };
19 enum BusType {
20 GPIOBUS,
21 I2CBUS,
22 SPIBUS
23 };
24
25 String name;
26 DisplayType displayType;
27 uint16_t resX, resY;
28 uint32_t bgColor;
29 bool isLightTheme;
30 uint8_t i2cAddress;
31 TwoWire *pWire;
32 uint8_t csPin, dcPin, rstPin;
33 BusType busType;
34 bool validDisplay;
35 bool useCanvas;
36 bool hasBegun;
37 Adafruit_ST7735 *pDisplayST;
38 Adafruit_SSD1306 *pDisplaySSD;
39 GFXcanvas16 *pCanvas;
40
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) {
43 pDisplayST = nullptr;
44 pDisplaySSD = nullptr;
45 pCanvas = nullptr;
46 hasBegun = false;
47 if (displayType == DisplayType::SSD1306) {
48 validDisplay = true;
49 busType = BusType::I2CBUS;
50 bgColor = SSD1306_BLACK;
51 } else {
52 validDisplay = false;
53 }
54 }
55
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) {
58 pDisplayST = nullptr;
59 pDisplaySSD = nullptr;
60 pCanvas = nullptr;
61 hasBegun = false;
62 if (displayType == DisplayType::ST7735) {
63 validDisplay = true;
64 busType = BusType::SPIBUS;
65 bgColor = RGB(0, 0, 0);
66 } else {
67 validDisplay = false;
68 }
69 }
70
71 ~GfxDrivers() { // undefined behavior for delete display objects.
72 }
73
74 void begin(bool _useCanvas = false) {
79 if (hasBegun) {
80#ifdef USE_SERIAL_DBG
81 Serial.println("ERROR GfxDrivers::begin() - already begun");
82#endif
83 return;
84 }
85 hasBegun = true;
86 useCanvas = _useCanvas;
87 if (validDisplay) {
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);
96 break;
97 case DisplayType::ST7735:
98 // pDisplayST->initR(INITR_BLACKTAB);
99 if (resX == 128 && resY == 128) {
100 pDisplayST = new Adafruit_ST7735(csPin, dcPin, rstPin);
101 pDisplayST->initR(INITR_144GREENTAB); // 1.4" thingy?
102 } else if (resX == 128 && resY == 160) {
103 pDisplayST = new Adafruit_ST7735(csPin, dcPin, rstPin);
104 pDisplayST->initR(INITR_BLACKTAB); // 1.8" thingy?
105 } else {
106#ifdef USE_SERIAL_DBG
107 Serial.println("ERROR GfxDrivers::begin() - unknown/invalid display resolution");
108#endif
109 hasBegun = false;
110 return;
111 }
112 if (useCanvas) {
113 pCanvas = new GFXcanvas16(resX, resY);
114 pCanvas->setTextWrap(false);
115 pCanvas->fillScreen(ST77XX_BLACK);
116 pCanvas->cp437(true);
117 } else {
118 pDisplayST->setTextWrap(false);
119 pDisplayST->fillScreen(ST77XX_BLACK);
120 pDisplayST->cp437(true);
121 }
122 break;
123 }
124 }
125 }
126
127 void setBGColor(uint32_t _bgColor) {
128 uint8_t r, g, b;
129 splitRGB(_bgColor, &r, &g, &b);
130 if (r + b + g > 256 + 128) {
131 isLightTheme = true;
132 } else {
133 isLightTheme = false;
134 }
135 bgColor = _bgColor;
136 }
137
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);
140 return rgb;
141 }
142
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);
147 }
148
149 uint16_t rgbColor(uint8_t red, uint8_t green, uint8_t blue) {
150 switch (displayType) {
151 case DisplayType::SSD1306: // Black or white
152 if (RGB(red, green, blue) == bgColor) {
153 if (isLightTheme)
154 return SSD1306_WHITE;
155 else
156 return SSD1306_BLACK;
157 } else {
158 if (isLightTheme)
159 return SSD1306_BLACK;
160 else
161 return SSD1306_WHITE;
162 }
163 case DisplayType::ST7735: // RGB565 standard
164 return (((red & 0xf8) << 8) + ((green & 0xfc) << 3) + (blue >> 3));
165 default:
166 return 0;
167 }
168 }
169
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);
174 }
175
176 void clearDisplay(uint32_t bgColor) {
177 if (validDisplay) {
178 switch (displayType) {
179 case DisplayType::SSD1306:
180 pDisplaySSD->clearDisplay();
181 pDisplaySSD->fillRect(0, 0, resX, resY, bgColor);
182 break;
183 case DisplayType::ST7735:
184 if (useCanvas) {
185 pCanvas->fillScreen(bgColor);
186 // pCanvas->fillRect(0, 0, resX, resY, bgColor);
187 } else {
188 pDisplayST->fillScreen(bgColor);
189 // pDisplayST->fillRect(0, 0, resX, resY, bgColor);
190 }
191 break;
192 default:
193 break;
194 }
195 }
196 }
197
198 void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t rgb) {
199 if (validDisplay) {
200 if (!hasBegun) {
201#ifdef USE_SERIAL_DBG
202 Serial.println("ERROR GfxDrivers::drawLine() - not begun");
203#endif
204 return;
205 }
206 switch (displayType) {
207 case DisplayType::SSD1306:
208 pDisplaySSD->drawLine(x0, y0, x1, y1, rgbColor(rgb));
209 break;
210 case DisplayType::ST7735:
211 if (useCanvas && pCanvas) {
212 if (y0 == y1) {
213 pCanvas->drawFastHLine(x0, y0, x1 - x0, rgbColor(rgb));
214 } else {
215 pCanvas->drawLine(x0, y0, x1, y1, rgbColor(rgb));
216 }
217 } else {
218 if (pDisplayST) {
219 pDisplayST->drawLine(x0, y0, x1, y1, rgbColor(rgb));
220 }
221 }
222 break;
223 default:
224 break;
225 }
226 }
227 }
228
229 void fillRect(uint16_t x0, uint16_t y0, uint16_t lx, uint16_t ly, uint32_t rgb) {
230 if (validDisplay) {
231 switch (displayType) {
232 case DisplayType::SSD1306:
233 pDisplaySSD->fillRect(x0, y0, lx, ly, rgbColor(rgb));
234 break;
235 case DisplayType::ST7735:
236 if (useCanvas) {
237 pCanvas->fillRect(x0, y0, lx, ly, rgbColor(rgb));
238 } else {
239 pDisplayST->fillRect(x0, y0, lx, ly, rgbColor(rgb));
240 }
241 break;
242 default:
243 break;
244 }
245 }
246 }
247
248 void setFont(const GFXfont *gfxFont = NULL) {
249 if (validDisplay) {
250 switch (displayType) {
251 case DisplayType::SSD1306:
252 pDisplaySSD->setFont(gfxFont);
253 break;
254 case DisplayType::ST7735:
255 if (useCanvas) {
256 pCanvas->setFont(gfxFont);
257 } else {
258 pDisplayST->setFont(gfxFont);
259 }
260 break;
261 default:
262 break;
263 }
264 }
265 }
266
267 void setTextColor(uint32_t rgb) {
268 if (validDisplay) {
269 switch (displayType) {
270 case DisplayType::SSD1306:
271 pDisplaySSD->setTextColor(rgbColor(rgb));
272 break;
273 case DisplayType::ST7735:
274 if (useCanvas) {
275 pCanvas->setTextColor(rgbColor(rgb));
276 } else {
277 pDisplayST->setTextColor(rgbColor(rgb));
278 }
279 break;
280 default:
281 break;
282 }
283 }
284 }
285
286 void setTextSize(uint16_t textSize) {
287 if (validDisplay) {
288 switch (displayType) {
289 case DisplayType::SSD1306:
290 pDisplaySSD->setTextSize(textSize);
291 break;
292 case DisplayType::ST7735:
293 if (useCanvas) {
294 pCanvas->setTextSize(textSize);
295 } else {
296 pDisplayST->setTextSize(textSize);
297 }
298 break;
299 default:
300 break;
301 }
302 }
303 }
304
305 void setCursor(uint16_t x, uint16_t y) {
306 if (validDisplay) {
307 switch (displayType) {
308 case DisplayType::SSD1306:
309 pDisplaySSD->setCursor(x, y);
310 break;
311 case DisplayType::ST7735:
312 if (useCanvas) {
313 pCanvas->setCursor(x, y);
314 } else {
315 pDisplayST->setCursor(x, y);
316 }
317 break;
318 default:
319 break;
320 }
321 }
322 }
323
324 void println(String &text) {
325 if (validDisplay) {
326 switch (displayType) {
327 case DisplayType::SSD1306:
328 pDisplaySSD->println(text);
329 break;
330 case DisplayType::ST7735:
331 if (useCanvas) {
332 pCanvas->println(text);
333 } else {
334 pDisplayST->println(text);
335 }
336 break;
337 default:
338 break;
339 }
340 }
341 }
342
343 void display() {
344 if (validDisplay) {
345 switch (displayType) {
346 case DisplayType::SSD1306:
347 pDisplaySSD->display();
348 break;
349 case DisplayType::ST7735:
350 if (useCanvas) {
351 pDisplayST->drawRGBBitmap(0, 0, pCanvas->getBuffer(), resX, resY);
352 }
353 break;
354 default:
355 break;
356 }
357 }
358 }
359};
360
361// clang-format off
462// clang-format on
463class GfxPanel {
464 public:
465 String name;
466 GfxDrivers::DisplayType displayType;
467 uint16_t resX, resY;
468 uint16_t slotResX, slotResY;
469 uint8_t i2cAddress;
470 TwoWire *pWire;
471 uint8_t csPin, dcPin, rstPin;
472 String locale;
473 bool active;
474
475 GfxDrivers *pDisplay;
476 ustd::Scheduler *pSched;
477#if defined(USTD_FEATURE_NETWORK) && !defined(OPTION_NO_MQTT)
478 ustd::Mqtt *pMqtt;
479#endif
480
481 enum SlotType { NUMBER,
482 TEXT,
483 GRAPH };
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; // 24 hours in ms for entire history
493 typedef struct t_slot {
494 bool isInit;
495 bool isValid;
496 bool hasChanged;
497 time_t lastUpdate;
498 SlotType slotType;
499
500 uint8_t slotX;
501 uint8_t slotY;
502 uint8_t slotLenX;
503 uint8_t slotLenY;
504 uint32_t color;
505 uint32_t bgColor;
506
507 String topic;
508 String caption;
509
510 uint16_t histLen;
511 uint32_t lastHistUpdate;
512 uint32_t histSampleRateMs;
513 float *pHist;
514 bool histInit;
515
516 float currentValue;
517 String currentText;
518 uint8_t digits;
519 float scalingFactor;
520 float offset;
521 float deltaDir;
522 uint32_t lastFrame;
523 uint32_t frameRate;
524 } T_SLOT;
525 uint16_t slots;
526 T_SLOT *pSlots;
527
528 String layout;
529 String formats;
530 ustd::array<String> topics;
531 ustd::array<String> captions;
532 ustd::array<String> msgs;
533
534 uint32_t displayFrameRateMs;
535 uint32_t lastRefresh;
536 uint32_t minUpdateIntervalMs;
537
538 String valid_formats = " SIPFDTG";
539 String valid_formats_long = " SIPFDTG";
540 String valid_formats_small = " sipfdtg";
541 char oldTimeString[64] = "";
542
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)
554 {
555 switch (displayType) {
556 case GfxDrivers::DisplayType::SSD1306:
557 pDisplay = new GfxDrivers(name, displayType, resX, resY, i2cAddress, pWire);
558 break;
559 default:
560 pDisplay = nullptr;
561 }
562 _common_init();
563 }
564
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)
577 {
578 switch (displayType) {
579 case GfxDrivers::DisplayType::ST7735:
580 pDisplay = new GfxDrivers(name, displayType, resX, resY, csPin, dcPin, rstPin);
581 break;
582 default:
583 pDisplay = nullptr;
584 }
585 _common_init();
586 }
587
588 ~GfxPanel() {
589 if (pDisplay) {
590 delete pDisplay;
591 }
592 if (pSlots) {
593 for (uint16_t i = 0; i < slots; i++) {
594 if (pSlots[i].pHist) {
595 delete pSlots[i].pHist;
596 }
597 }
598 }
599 if (pSlots) {
600 delete[] pSlots;
601 }
602 }
603
604 private:
605 uint32_t relRGB(uint8_t r, uint8_t g, uint8_t b, float brightness, float contrast) {
606 int16_t rt, gt, bt;
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);
617 }
618
619 String themeName;
620 float brightness, contrast;
621 enum Theme { ThemeDark,
622 ThemeLight,
623 ThemeGruvbox,
624 ThemeSolarizedDark,
625 ThemeSolarizedLight };
626 Theme themeType;
627 void _setTheme(Theme theme) {
628 themeType = Theme::ThemeDark;
629 switch (theme) {
630 case ThemeLight:
631 themeName = "light";
632 themeType = theme;
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);
640 break;
641 case ThemeSolarizedLight:
642 themeName = "solarizedlight";
643 themeType = theme;
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);
651 break;
652 case ThemeDark:
653 default:
654 themeName = "dark";
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);
663 break;
664 }
665 for (uint16_t slot = 0; slot < slots; slot++) {
666 pSlots[slot].color = defaultColor;
667 pSlots[slot].bgColor = defaultBgColor;
668 }
669 pDisplay->setBGColor(defaultBgColor);
670#ifdef USE_SERIAL_DBG
671 Serial.println("setTheme: " + themeName);
672#endif
673 }
674
675 void _common_init() {
676 active = false;
677 displayFrameRateMs = 1000;
678 slotResX = 64;
679 slotResY = 32;
680 brightness = 0.5;
681 contrast = 0.5;
682#if USTD_FEATURE_MEMORY >= USTD_FEATURE_MEM_128K
683 defaultHistLen = 128;
684#elif USTD_FEATURE_MEMORY >= USTD_FEATURE_MEM_32K
685 defaultHistLen = 64;
686#else
687 defaultHistLen = 16;
688#endif
689 defaultHistSampleRateMs = 3600 * 1000 / 64; // 1 hr in ms for entire history
690 }
691
692 bool splitCombinedLayout(String combined_layout) {
693 bool layout_valid = true;
694 bool parsing = true;
695
696 formats = "";
697 layout = "";
698 slots = 0;
699 String line = "";
700
701 while (parsing) {
702 int ind = combined_layout.indexOf('|');
703 if (ind == -1) {
704 line = combined_layout;
705 combined_layout = "";
706 } else {
707 line = combined_layout.substring(0, ind);
708 combined_layout = combined_layout.substring(ind + 1);
709 }
710 for (char c : line) {
711 if (valid_formats_small.indexOf(c) == -1 && valid_formats_long.indexOf(c) == -1) {
712 layout_valid = false;
713 parsing = false;
714 break;
715 } else {
716 ind = valid_formats_small.indexOf(c);
717 if (ind != -1) {
718 c = valid_formats_long[ind];
719 layout += "S";
720 } else {
721 layout += "L";
722 }
723 formats += c;
724 ++slots;
725 }
726 }
727 if (!layout_valid) break;
728 if (combined_layout != "") {
729 layout += "|";
730 } else {
731 parsing = false;
732 }
733 }
734
735 lastRefresh = 0;
736 return layout_valid;
737 }
738
739 bool getConfigFromFS(String name) {
744 String combined_layout = jf.readString(name + "/layout", "ff|ff");
745 if (!splitCombinedLayout(combined_layout)) {
746 return false;
747 }
748 for (uint8_t i = 0; i < slots; i++) {
749 captions[i] = "room";
750 topics[i] = "some/topic";
751 }
752 jf.readStringArray(name + "/topics", topics);
753 jf.readStringArray(name + "/captions", captions);
754 if (topics.length() != captions.length() || topics.length() != slots) {
755#ifdef USE_SERIAL_DBG
756 Serial.println("Error: topics, captions and layout do not match");
757#endif
758 return false;
759 }
760 return true;
761 }
762
763 bool getConfigFromLayout(String name, String combined_layout) {
769 if (!splitCombinedLayout(combined_layout)) {
770 return false;
771 }
772 if (topics.length() != captions.length() || topics.length() != slots) {
773#ifdef USE_SERIAL_DBG
774 Serial.println("Error: topics, captions and layout do not match");
775#endif
776 return false;
777 }
778 return true;
779 }
780
781 bool config2slot(uint16_t slot) {
786 if (slot >= slots) {
787 return false;
788 }
789 pSlots[slot].slotX = 0;
790 pSlots[slot].slotY = 0;
791 uint16_t ind = 0;
792 for (auto c : layout) {
793 if (c == 'S') {
794 if (ind == slot) {
795 pSlots[slot].slotLenX = 1;
796 pSlots[slot].slotLenY = 1;
797 break;
798 } else {
799 pSlots[slot].slotX++;
800 }
801 ++ind;
802 } else if (c == 'L') {
803 if (ind == slot) {
804 pSlots[slot].slotLenX = 2;
805 pSlots[slot].slotLenY = 1;
806 break;
807 } else {
808 pSlots[slot].slotX += 2;
809 }
810 ++ind;
811 } else if (c == '|') {
812 pSlots[slot].slotY++;
813 pSlots[slot].slotX = 0;
814 } else {
815 return false;
816 }
817 }
818 pSlots[slot].histLen = defaultHistLen;
819 pSlots[slot].offset = 0.0;
820 pSlots[slot].scalingFactor = 1.0;
821 switch (formats[slot]) {
822 case 'I':
823 pSlots[slot].slotType = SlotType::NUMBER;
824 pSlots[slot].digits = 0;
825 break;
826 case 'F':
827 pSlots[slot].slotType = SlotType::NUMBER;
828 pSlots[slot].digits = 1;
829 break;
830 case 'D':
831 pSlots[slot].slotType = SlotType::NUMBER;
832 pSlots[slot].digits = 2;
833 break;
834 case 'T':
835 pSlots[slot].slotType = SlotType::NUMBER;
836 pSlots[slot].digits = 3;
837 break;
838 case 'S':
839 pSlots[slot].slotType = SlotType::TEXT;
840 pSlots[slot].digits = 3;
841 pSlots[slot].histLen = 0;
842 break;
843 case 'P':
844 pSlots[slot].slotType = SlotType::NUMBER;
845 pSlots[slot].scalingFactor = 100.0;
846 pSlots[slot].digits = 1;
847 break;
848 case 'G':
849 pSlots[slot].slotType = SlotType::GRAPH;
850 pSlots[slot].digits = 3;
851 break;
852 default:
853 return false;
854 }
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;
861 }
862 } else {
863 pSlots[slot].histLen = 0;
864 return false;
865 }
866 } else {
867 pSlots[slot].pHist = nullptr;
868 pSlots[slot].histLen = 0;
869 }
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;
884 return true;
885 }
886
887 bool shortConfig2Slots() {
891 if (formats.length() != slots) {
892#ifdef USE_SERIAL_DBG
893 Serial.println("Error: formats and slots number do not match");
894#endif
895 return false;
896 }
897 slots = formats.length();
898 pSlots = new T_SLOT[slots];
899 for (uint16_t i = 0; i < slots; i++) {
900 if (!config2slot(i)) {
901 return false;
902 }
903 }
904 return true;
905 }
906
907 void _sensorLoop() {
908 const char *weekDays[] = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"};
909 const char *wochenTage[] = {"So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"};
910 const char *pDay;
911 struct tm *plt;
912 time_t t;
913 char buf[64];
914 if (!active) return;
915 // scruffy old c time functions
916 t = time(nullptr);
917 plt = localtime(&t);
918 if (locale == "DE")
919 pDay = wochenTage[plt->tm_wday];
920 else
921 pDay = weekDays[plt->tm_wday];
922 // sprintf(buf,"%s %02d. %02d:%02d",pDay,plt->tm_mday, plt->tm_hour, plt->tm_min);
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");
927 }
928 // If a sensors doesn't update values for 1 hr (3600sec), declare invalid.
929 for (uint8_t i = 0; i < slots; i++) {
930 if (time(nullptr) - pSlots[i].lastUpdate > 3600) {
931 pSlots[i].isValid = false;
932 }
933 if (pSlots[i].slotType == SlotType::GRAPH) {
934 if (!pSlots[i].hasChanged) {
935 updateSlot(i, pSlots[i].currentText); // force update with same value for scrolling graph
936 }
937 }
938 }
939 updateDisplay(false, false);
940 }
941
942 void commonBegin(bool useCanvas = false) {
943 pDisplay->begin(useCanvas);
944
945 auto fntsk = [=]() {
946 _sensorLoop();
947 };
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);
952 };
953 pSched->subscribe(tID, name + "/display/#", fnsub);
954 auto fnall = [=](String topic, String msg, String originator) {
955 sensorUpdates(topic, msg, originator);
956 };
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);
963#ifdef USE_SERIAL_DBG
964 Serial.print("Subscribing via MQTT: ");
965 Serial.println(topics[i]);
966#endif
967 }
968#endif
969 if (topics[i][0] != '!') {
970 if (topics[i] != "clock/timeinfo") { // local shortcut msg. Questionable?
971 pSched->subscribe(tID, topics[i], fnall);
972#ifdef USE_SERIAL_DBG
973 Serial.print("Subscribing internally: ");
974 Serial.println(topics[i]);
975#endif
976 } else {
977#ifdef USE_SERIAL_DBG
978 Serial.print("Internal topic: ");
979 Serial.println(topics[i]);
980#endif
981 }
982 }
983 } else {
984 // No sub for empty topics.
985 }
986 }
987#ifdef USE_SERIAL_DBG
988 Serial.print("Layout: ");
989 Serial.print(layout);
990 Serial.print(" formats: ");
991 Serial.print(formats);
992 Serial.print(" histLen: ");
993 Serial.println(defaultHistLen);
994#endif // USE_SERIAL_DBG
995 shortConfig2Slots();
996 active = true;
997 _setTheme(Theme::ThemeDark);
998 updateDisplay(true, true);
999 }
1000
1001 public:
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);
1009 }
1010
1011 void publishBrightness() {
1012 if (!active) return;
1013 pSched->publish(name + "/display/brightness", String(brightness, 3));
1014 }
1015
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);
1023 }
1024
1025 void publishContrast() {
1026 if (!active) return;
1027 pSched->publish(name + "/display/contrast", String(contrast, 3));
1028 }
1029
1030 void setTheme(String _theme) {
1031 if (!active) return;
1032 bool upd = false;
1033 contrast = 0.5;
1034 brightness = 0.5;
1035 if (_theme == "light") {
1036 _setTheme(Theme::ThemeLight);
1037 upd = true;
1038 }
1039 if (_theme == "solarizedlight") {
1040 _setTheme(Theme::ThemeSolarizedLight);
1041 upd = true;
1042 }
1043 if (!upd) {
1044 _setTheme(Theme::ThemeDark);
1045 }
1046 updateDisplay(true, true);
1047 }
1048
1049 void publishTheme() {
1050 if (!active) return;
1051 pSched->publish(name + "/display/theme", themeName);
1052 }
1053
1054 void setSlotCaption(uint16_t slot, String caption) {
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;
1065 }
1066 }
1067 updateDisplay(false, false);
1068 }
1069
1070 void publishSlotCaption(uint16_t slot) {
1074 if (!active) return;
1075 String cap = "";
1076 bool done = false;
1077 if (slot < slots) {
1078 if (pSlots) {
1079 if (pSlots[slot].isInit) {
1080 cap = pSlots[slot].caption;
1081 done = true;
1082 }
1083 }
1084 if (!done) {
1085 cap = captions[slot];
1086 }
1087 pSched->publish(name + "/display/slot/" + String(slot) + "/caption", cap);
1088 }
1089 }
1090
1091 void setSlotText(uint16_t slot, String text) {
1096 if (!active) return;
1097 if (slot < slots) {
1098 // XXX
1099 }
1100 }
1101 void publishSlotText(uint16_t slot) {
1105 if (!active) return;
1106 if (slot < slots) {
1107 // pSched->publish(name+"/display/slot/"+String(slot), xxx[slot]);
1108 }
1109 }
1110
1111 void setSlotTopic(uint16_t slot, String topic) {
1116 if (!active) return;
1117 }
1118 void publishSlotTopic(uint16_t slot) {
1122 if (!active) return;
1123 }
1124 void setSlotFormat(uint16_t slot, String format) {
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);
1134 }
1135 }
1136 void publishSlotFormat(uint16_t slot) {
1140 if (!active) return;
1141 }
1142
1143 void setSlotHistorySampleRateMs(uint16_t slot, uint32_t rate) {
1148 if (!active) return;
1149 if (slot < slots) {
1150 pSlots[slot].histSampleRateMs = rate;
1151 pSlots[slot].frameRate = rate;
1152 if (rate < displayFrameRateMs) {
1153 displayFrameRateMs = rate;
1154 }
1155 }
1156 }
1157
1162 if (!active) return;
1163 if (slot < slots) {
1164 pSched->publish(name + "/display/slot/" + String(slot) + "/histosrysampleratems", String(pSlots[slot].histSampleRateMs));
1165 }
1166 }
1167#if defined(USTD_FEATURE_NETWORK) && !defined(OPTION_NO_MQTT)
1168 void begin(ustd::Scheduler *_pSched, ustd::Mqtt *_pMqtt, bool _useCanvas = false) {
1175 pSched = _pSched;
1176 pMqtt = _pMqtt;
1177#else
1178 void begin(ustd::Scheduler *_pSched, bool _useCanvas = false) {
1184 pSched = _pSched;
1185#endif
1186 getConfigFromFS(name);
1187 commonBegin(_useCanvas);
1188 }
1189
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) {
1201 pSched = _pSched;
1202 pMqtt = _pMqtt;
1203#else
1204 void begin(ustd::Scheduler *_pSched, String combined_layout, ustd::array<String> _topics, ustd::array<String> _captions, bool _useCanvas = false) {
1213 pSched = _pSched;
1214#endif
1215 for (auto t : _topics) {
1216 topics.add(t);
1217 }
1218 for (auto c : _captions) {
1219 captions.add(c);
1220 }
1221
1222 getConfigFromLayout(name, combined_layout);
1223 commonBegin(_useCanvas);
1224 }
1225
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) {
1238 pSched = _pSched;
1239 pMqtt = _pMqtt;
1240#else
1241 void begin(ustd::Scheduler *_pSched, String combined_layout, uint16_t _slots, const char *_topics[], const char *_captions[], bool _useCanvas = false) {
1251 pSched = _pSched;
1252#endif
1253 for (uint16_t i = 0; i < _slots; i++) {
1254 String s = _topics[i];
1255 topics.add(s);
1256 String c = _captions[i];
1257 captions.add(c);
1258 }
1259 getConfigFromLayout(name, combined_layout);
1260 commonBegin(_useCanvas);
1261 }
1262
1263 private:
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;
1267 if (up) {
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);
1274 } else {
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);
1281 }
1282 }
1283
1284 void boldParser(String msg, String &first, String &sec) {
1285 bool isBold = true;
1286 for (unsigned int i = 0; i < msg.length(); i++) {
1287 if (msg[i] == '_') {
1288 isBold = !isBold;
1289 continue;
1290 }
1291 if (isBold) {
1292 first += msg[i];
1293 sec += msg[i];
1294 } else {
1295 first += msg[i];
1296 sec += ' ';
1297 }
1298 }
1299 }
1300
1301 bool displaySlot(uint16_t slot) {
1302 if (slot >= slots) return false;
1303
1304 uint8_t x0 = 0, y0 = 0, x1 = 0, y1 = 0, xa = 0, ya = 0;
1305 uint8_t xm0, ym0, xm1, ym1;
1306
1307 // Blank
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);
1314 // Caption font start x0,y0
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;
1325
1326 // caption
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);
1336
1337 float gmin, gmax, dmin, dmax;
1338 float avg, navg;
1339 dmin = 1000000000.0;
1340 dmax = -1000000000.0;
1341 gmax = -1000000000.0;
1342 gmin = 1000000000.0;
1343 avg = 0.0;
1344 navg = 0.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];
1354 navg++;
1355 if (pSlots[slot].pHist[x] > dmax) dmax = pSlots[slot].pHist[x];
1356 if (pSlots[slot].pHist[x] < dmin) dmin = pSlots[slot].pHist[x];
1357 }
1358 }
1359 float ref;
1360 if (navg > 0.0)
1361 ref = avg / navg;
1362 else
1363 ref = 0.0;
1364 pSlots[slot].deltaDir = pSlots[slot].currentValue - ref;
1365 }
1366 }
1367 if (pSlots[slot].slotType != SlotType::GRAPH) {
1368 // Main text
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) {
1377 // arrow
1378 if (pSlots[slot].deltaDir != 0.0) {
1379 drawArrow(xa, ya, (pSlots[slot].deltaDir > 0.0), 8, 3, 7);
1380 }
1381 }
1382 } else {
1383 // Graph
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; // font size of caption.
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));
1395 uint32_t col;
1396 if (ly1 < ly0)
1397 col = defaultIncreaseColor;
1398 else {
1399 if (ly1 == ly0)
1400 col = defaultConstColor;
1401 else
1402 col = defaultDecreaseColor;
1403 }
1404 pDisplay->drawLine(lx0, ly0, lx1, ly1, col);
1405 }
1406 }
1407 }
1408 pSlots[slot].hasChanged = false;
1409 return true;
1410 }
1411
1412 public:
1413 void updateDisplay(bool updateNow, bool forceRedraw = false) {
1414 if (!updateNow && timeDiff(lastRefresh, millis()) < displayFrameRateMs) {
1415 return;
1416 }
1417 bool update = false;
1418 lastRefresh = millis();
1419 uint16_t maxSlotX = 0, maxSlotY = 0;
1420
1421 if (forceRedraw) {
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;
1426 }
1427 uint16_t y;
1428 for (uint16_t ly = 0; ly <= maxSlotY + 1; ly++) {
1429 y = ly * slotResY;
1430 if (y >= resY) y = resY - 1;
1431 // XXX slotLenY==1! (maybe implicitly solved by rect fill)
1432 pDisplay->drawLine(0, y, resX - 1, y, defaultSeparatorColor);
1433 }
1434 }
1435 for (uint16_t slot = 0; slot < slots; slot++) {
1436 if (pSlots[slot].hasChanged || forceRedraw) {
1437 if (displaySlot(slot)) update = true;
1438 }
1439 }
1440 if (update || forceRedraw) {
1441 pDisplay->display();
1442 }
1443 }
1444
1445 private:
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) {
1455 changed = true;
1456 }
1457 pSlots[slot].currentText = msg;
1458 pSlots[slot].isValid = true;
1459 pSlots[slot].lastUpdate = time(nullptr);
1460 pSlots[slot].lastFrame = millis();
1461 break;
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) {
1470 changed = true;
1471 }
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;
1477 }
1478 pSlots[slot].lastHistUpdate = millis();
1479 pSlots[slot].histInit = true;
1480 changed = true;
1481 } else {
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];
1485 }
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) {
1489 changed = true;
1490 }
1491 }
1492 pSlots[slot].pHist[pSlots[slot].histLen - 1] = pSlots[slot].currentValue;
1493 }
1494 }
1495 break;
1496 }
1497 pSlots[slot].hasChanged = changed;
1498 return changed;
1499 }
1500
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;
1507 }
1508 }
1509 if (changed) updateDisplay(false, false);
1510 }
1511
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("/");
1518 if (ind != -1) {
1519 int16_t slot = sub.substring(0, ind).toInt();
1520 if (slot < slots) {
1521 String action = sub.substring(ind + 1);
1522 if (action == "caption/get") {
1523 publishSlotCaption(slot);
1524 } else if (action == "caption/set") {
1525 setSlotCaption(slot, msg);
1526 } else if (action == "format/get") {
1527 publishSlotFormat(slot);
1528 } else if (action == "format/set") {
1529 setSlotFormat(slot, msg);
1530 } else if (action == "topic/get") {
1531 publishSlotTopic(slot);
1532 } else if (action == "topic/set") {
1533 setSlotTopic(slot, msg);
1534 } else if (action == "text/get") {
1535 publishSlotText(slot);
1536 } else if (action == "text/set") {
1537 setSlotText(slot, msg);
1538 } else if (action == "historysampleratems/get") {
1540 } else if (action == "historysampleratems/set") {
1541 setSlotHistorySampleRateMs(slot, msg.toInt());
1542 }
1543 }
1544 }
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;
1549 setBrightness(br);
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;
1556 setContrast(c);
1557 } else if (topic == name + "/display/contrast/get") {
1558 publishContrast();
1559 } else if (topic == name + "/display/theme/set") {
1560 setTheme(msg);
1561 } else if (topic == name + "/display/theme/get") {
1562 publishTheme();
1563 }
1564 }
1565
1566}; // SensorDisplay
1567} // namespace ustd
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