4LineDisplay rewrite for dynamic configuration.
Added handling for multiple pins in usermod. Fixed minor bugs.
This commit is contained in:
parent
13b3b2fd23
commit
a6feb77e52
@ -24,50 +24,25 @@
|
||||
//
|
||||
|
||||
//The SCL and SDA pins are defined here.
|
||||
#ifndef FLD_PIN_SCL
|
||||
#define FLD_PIN_SCL 5
|
||||
#endif
|
||||
|
||||
#ifndef FLD_PIN_SDA
|
||||
#define FLD_PIN_SDA 4
|
||||
#endif
|
||||
|
||||
// U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(
|
||||
// U8X8_PIN_NONE, FLD_PIN_SCL, FLD_PIN_SDA);
|
||||
U8X8_SH1106_128X64_WINSTAR_HW_I2C u8x8(
|
||||
U8X8_PIN_NONE, FLD_PIN_SCL, FLD_PIN_SDA);
|
||||
|
||||
// Screen upside down? Change to 0 or 1
|
||||
#ifndef FLIP_MODE
|
||||
#define FLIP_MODE 0
|
||||
#endif
|
||||
|
||||
// LINE_HEIGHT 1 is single height, for 128x32 displays.
|
||||
// LINE_HEIGHT 2 makes the 128x64 screen display at double height.
|
||||
#ifndef LINE_HEIGHT
|
||||
#define LINE_HEIGHT 2
|
||||
#endif
|
||||
|
||||
// If you aren't also including RotaryEncoderUIUsermod
|
||||
// you probably want to set both
|
||||
// SLEEP_MODE_ENABLED false
|
||||
// CLOCK_MODE_ENABLED false
|
||||
// as you will never be able wake the display / disable the clock.
|
||||
#ifdef USERMOD_ROTARY_ENCODER_UI
|
||||
#ifndef SLEEP_MODE_ENABLED
|
||||
#define SLEEP_MODE_ENABLED true
|
||||
#endif
|
||||
#ifndef CLOCK_MODE_ENABLED
|
||||
#define CLOCK_MODE_ENABLED true
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifndef FLD_PIN_SCL
|
||||
#define FLD_PIN_SCL 22
|
||||
#endif
|
||||
#ifndef FLD_PIN_SDA
|
||||
#define FLD_PIN_SDA 21
|
||||
#endif
|
||||
#else
|
||||
#define SLEEP_MODE_ENABLED false
|
||||
#define CLOCK_MODE_ENABLED false
|
||||
#ifndef FLD_PIN_SCL
|
||||
#define FLD_PIN_SCL 5
|
||||
#endif
|
||||
#ifndef FLD_PIN_SDA
|
||||
#define FLD_PIN_SDA 4
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// When to time out to the clock or blank the screen
|
||||
// if SLEEP_MODE_ENABLED.
|
||||
#define SCREEN_TIMEOUT_MS 15*1000
|
||||
#define SCREEN_TIMEOUT_MS 60*1000 // 1 min
|
||||
|
||||
#define TIME_INDENT 0
|
||||
#define DATE_INDENT 2
|
||||
@ -75,33 +50,39 @@ U8X8_SH1106_128X64_WINSTAR_HW_I2C u8x8(
|
||||
// Minimum time between redrawing screen in ms
|
||||
#define USER_LOOP_REFRESH_RATE_MS 1000
|
||||
|
||||
#if LINE_HEIGHT == 2
|
||||
#define DRAW_STRING draw1x2String
|
||||
#define DRAW_GLYPH draw1x2Glyph
|
||||
#define DRAW_BIG_STRING draw2x2String
|
||||
#else
|
||||
#define DRAW_STRING drawString
|
||||
#define DRAW_GLYPH drawGlyph
|
||||
#define DRAW_BIG_STRING draw2x2String
|
||||
#endif
|
||||
|
||||
// Extra char (+1) for null
|
||||
#define LINE_BUFFER_SIZE 16+1
|
||||
#define FLD_LINE_3_BRIGHTNESS 0
|
||||
#define FLD_LINE_3_EFFECT_SPEED 1
|
||||
#define FLD_LINE_3_EFFECT_INTENSITY 2
|
||||
#define FLD_LINE_3_PALETTE 3
|
||||
|
||||
#if LINE_HEIGHT == 2
|
||||
#define TIME_LINE 1
|
||||
#else
|
||||
#define TIME_LINE 0
|
||||
#endif
|
||||
typedef enum {
|
||||
FLD_LINE_3_BRIGHTNESS = 0,
|
||||
FLD_LINE_3_EFFECT_SPEED,
|
||||
FLD_LINE_3_EFFECT_INTENSITY,
|
||||
FLD_LINE_3_PALETTE,
|
||||
FLD_LINE_3_TIME
|
||||
} Line3Type;
|
||||
|
||||
typedef enum {
|
||||
NONE = 0,
|
||||
SSD1306, // U8X8_SSD1306_128X32_UNIVISION_HW_I2C
|
||||
SH1106 // U8X8_SH1106_128X64_WINSTAR_HW_I2C
|
||||
} DisplayType;
|
||||
|
||||
class FourLineDisplayUsermod : public Usermod {
|
||||
private:
|
||||
unsigned long lastTime = 0;
|
||||
|
||||
// HW interface & configuration
|
||||
void *u8x8; // pointer to U8X8 display object
|
||||
int8_t sclPin, sdaPin; // I2C pins for interfacing, get initialised in readFromConfig()
|
||||
DisplayType type = NONE; // display type
|
||||
bool flip; // flip display 180°
|
||||
uint8_t contrast;
|
||||
uint8_t lineHeight; // 1 row or 2 rows
|
||||
uint32_t refreshRate; // in ms
|
||||
uint32_t screenTimeout; // in ms
|
||||
bool sleepMode; // allow screen sleep?
|
||||
bool clockMode; // display clock instead of info?
|
||||
|
||||
// needRedraw marks if redraw is required to prevent often redrawing.
|
||||
bool needRedraw = true;
|
||||
|
||||
@ -121,12 +102,10 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
long lastUpdate = 0;
|
||||
long lastRedraw = 0;
|
||||
long overlayUntil = 0;
|
||||
byte lineThreeType = FLD_LINE_3_BRIGHTNESS;
|
||||
Line3Type lineThreeType = FLD_LINE_3_BRIGHTNESS;
|
||||
// Set to 2 or 3 to mark lines 2 or 3. Other values ignored.
|
||||
byte markLineNum = 0;
|
||||
|
||||
char lineBuffer[LINE_BUFFER_SIZE];
|
||||
|
||||
char **modes_qstrings = nullptr;
|
||||
char **palettes_qstrings = nullptr;
|
||||
|
||||
@ -140,12 +119,30 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
// gets called once at boot. Do all initialization that doesn't depend on
|
||||
// network here
|
||||
void setup() {
|
||||
u8x8.begin();
|
||||
u8x8.setFlipMode(FLIP_MODE);
|
||||
u8x8.setPowerSave(0);
|
||||
u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
|
||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||
u8x8.DRAW_STRING(0, 0*LINE_HEIGHT, "Loading...");
|
||||
if (type==NONE) return;
|
||||
if (!pinManager.allocatePin(sclPin)) { sclPin = -1; type = NONE; return;}
|
||||
if (!pinManager.allocatePin(sdaPin)) { pinManager.deallocatePin(sclPin); sclPin = sdaPin = -1; type = NONE; return; }
|
||||
switch (type) {
|
||||
case SSD1306:
|
||||
u8x8 = (void *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
|
||||
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->begin();
|
||||
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->setFlipMode(flip);
|
||||
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
|
||||
break;
|
||||
case SH1106:
|
||||
u8x8 = (void *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
|
||||
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->begin();
|
||||
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->setFlipMode(flip);
|
||||
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
|
||||
break;
|
||||
default:
|
||||
u8x8 = nullptr;
|
||||
type = NONE;
|
||||
return;
|
||||
}
|
||||
setPowerSave(0);
|
||||
String loading = String(F("Loading..."));
|
||||
drawString(0, 0, loading.c_str());
|
||||
|
||||
ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT);
|
||||
modes_qstrings = modeSortUsermod->getModesQStrings();
|
||||
@ -160,7 +157,7 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
* Da loop.
|
||||
*/
|
||||
void loop() {
|
||||
if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) {
|
||||
if (millis() - lastUpdate < refreshRate) {
|
||||
return;
|
||||
}
|
||||
lastUpdate = millis();
|
||||
@ -168,18 +165,97 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
redraw(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrappers for screen drawing
|
||||
*/
|
||||
void drawString(uint8_t col, uint8_t row, const char *string) {
|
||||
if (type==NONE) return;
|
||||
switch (type) {
|
||||
case SSD1306:
|
||||
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->setFont(u8x8_font_chroma48medium8_r);
|
||||
if (lineHeight==2) (static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->draw1x2String(col, row, string);
|
||||
else (static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->drawString(col, row, string);
|
||||
break;
|
||||
case SH1106:
|
||||
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->setFont(u8x8_font_chroma48medium8_r);
|
||||
if (lineHeight==2) (static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->draw1x2String(col, row, string);
|
||||
else (static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->drawString(col, row, string);
|
||||
break;
|
||||
}
|
||||
}
|
||||
void draw2x2String(uint8_t col, uint8_t row, const char *string) {
|
||||
if (type==NONE) return;
|
||||
switch (type) {
|
||||
case SSD1306:
|
||||
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->setFont(u8x8_font_chroma48medium8_r);
|
||||
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->draw2x2String(col, row, string);
|
||||
break;
|
||||
case SH1106:
|
||||
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->setFont(u8x8_font_chroma48medium8_r);
|
||||
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->draw2x2String(col, row, string);
|
||||
break;
|
||||
}
|
||||
}
|
||||
void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font) {
|
||||
if (type==NONE) return;
|
||||
switch (type) {
|
||||
case SSD1306:
|
||||
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->setFont(font);
|
||||
if (lineHeight==2) (static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->draw1x2Glyph(col, row, glyph);
|
||||
else (static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->drawGlyph(col, row, glyph);
|
||||
break;
|
||||
case SH1106:
|
||||
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->setFont(font);
|
||||
if (lineHeight==2) (static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->draw1x2Glyph(col, row, glyph);
|
||||
else (static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->drawGlyph(col, row, glyph);
|
||||
break;
|
||||
}
|
||||
}
|
||||
uint8_t getCols() {
|
||||
if (type==NONE) return 255;
|
||||
switch (type) {
|
||||
case SSD1306:
|
||||
return (static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->getCols();
|
||||
case SH1106:
|
||||
return (static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->getCols();
|
||||
}
|
||||
}
|
||||
void clear() {
|
||||
if (type==NONE) return;
|
||||
switch (type) {
|
||||
case SSD1306:
|
||||
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->clear();
|
||||
break;
|
||||
case SH1106:
|
||||
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
void setPowerSave(uint8_t save) {
|
||||
if (type==NONE) return;
|
||||
switch (type) {
|
||||
case SSD1306:
|
||||
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->setPowerSave(save);
|
||||
break;
|
||||
case SH1106:
|
||||
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->setPowerSave(save);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redraw the screen (but only if things have changed
|
||||
* or if forceRedraw).
|
||||
*/
|
||||
void redraw(bool forceRedraw) {
|
||||
if (type==NONE) return;
|
||||
|
||||
if (overlayUntil > 0) {
|
||||
if (millis() >= overlayUntil) {
|
||||
// Time to display the overlay has elapsed.
|
||||
overlayUntil = 0;
|
||||
forceRedraw = true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// We are still displaying the overlay
|
||||
// Don't redraw.
|
||||
return;
|
||||
@ -208,22 +284,41 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
if (!needRedraw) {
|
||||
// Nothing to change.
|
||||
// Turn off display after 3 minutes with no change.
|
||||
if(SLEEP_MODE_ENABLED && !displayTurnedOff &&
|
||||
(millis() - lastRedraw > SCREEN_TIMEOUT_MS)) {
|
||||
if(sleepMode && !displayTurnedOff && (millis() - lastRedraw > screenTimeout)) {
|
||||
// We will still check if there is a change in redraw()
|
||||
// and turn it back on if it changed.
|
||||
sleepOrClock(true);
|
||||
}
|
||||
else if (displayTurnedOff && CLOCK_MODE_ENABLED) {
|
||||
} else if (displayTurnedOff && clockMode) {
|
||||
showTime();
|
||||
} else if ((millis() - lastRedraw)/1000%3 == 0) {
|
||||
// change 4th line every 3s
|
||||
switch (lineThreeType) {
|
||||
case FLD_LINE_3_BRIGHTNESS:
|
||||
setLineThreeType(FLD_LINE_3_PALETTE);
|
||||
break;
|
||||
case FLD_LINE_3_PALETTE:
|
||||
setLineThreeType(FLD_LINE_3_EFFECT_SPEED);
|
||||
break;
|
||||
case FLD_LINE_3_EFFECT_SPEED:
|
||||
setLineThreeType(FLD_LINE_3_EFFECT_INTENSITY);
|
||||
break;
|
||||
case FLD_LINE_3_EFFECT_INTENSITY:
|
||||
setLineThreeType(FLD_LINE_3_BRIGHTNESS);
|
||||
break;
|
||||
default:
|
||||
setLineThreeType(FLD_LINE_3_BRIGHTNESS);
|
||||
break;
|
||||
}
|
||||
drawLineThree();
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else
|
||||
clear();
|
||||
|
||||
needRedraw = false;
|
||||
lastRedraw = millis();
|
||||
|
||||
if (displayTurnedOff)
|
||||
{
|
||||
if (displayTurnedOff) {
|
||||
// Turn the display back on
|
||||
sleepOrClock(false);
|
||||
}
|
||||
@ -242,54 +337,55 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
knownEffectIntensity = effectIntensity;
|
||||
|
||||
// Do the actual drawing
|
||||
u8x8.clear();
|
||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||
|
||||
// First row with Wifi name
|
||||
String ssidString = knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0);
|
||||
u8x8.DRAW_STRING(1, 0*LINE_HEIGHT, ssidString.c_str());
|
||||
// Print `~` char to indicate that SSID is longer, than owr dicplay
|
||||
if (knownSsid.length() > u8x8.getCols()) {
|
||||
u8x8.DRAW_STRING(u8x8.getCols() - 1, 0*LINE_HEIGHT, "~");
|
||||
String ssidString = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0);
|
||||
drawString(1, 0, ssidString.c_str());
|
||||
// Print `~` char to indicate that SSID is longer, than our display
|
||||
if (knownSsid.length() > getCols()) {
|
||||
drawString(getCols() - 1, 0, "~");
|
||||
}
|
||||
|
||||
// Second row with IP or Psssword
|
||||
// Print password in AP mode and if led is OFF.
|
||||
if (apActive && bri == 0) {
|
||||
u8x8.DRAW_STRING(1, 1*LINE_HEIGHT, apPass);
|
||||
}
|
||||
else {
|
||||
String ipString = knownIp.toString();
|
||||
u8x8.DRAW_STRING(1, 1*LINE_HEIGHT, ipString.c_str());
|
||||
drawString(1, lineHeight, apPass);
|
||||
} else {
|
||||
drawString(1, lineHeight, (knownIp.toString()).c_str());
|
||||
}
|
||||
|
||||
// Third row with mode name
|
||||
showCurrentEffectOrPalette(modes_qstrings[knownMode], 2);
|
||||
|
||||
// Fourth row
|
||||
drawLineThree();
|
||||
|
||||
drawGlyph(0, 2, 66 + (bri > 0 ? 3 : 0), u8x8_font_open_iconic_weather_2x2); // sun/moon icon
|
||||
drawGlyph(0, 0, 80, u8x8_font_open_iconic_embedded_1x1); // wifi icon
|
||||
drawGlyph(0, lineHeight, 68, u8x8_font_open_iconic_embedded_1x1); // home icon
|
||||
//if (markLineNum>1) drawGlyph(2, markLineNum*lineHeight, 66, u8x8_font_open_iconic_arrow_1x1); // arrow icon
|
||||
}
|
||||
|
||||
void drawLineThree() {
|
||||
char lineBuffer[LINE_BUFFER_SIZE];
|
||||
switch(lineThreeType) {
|
||||
case FLD_LINE_3_BRIGHTNESS:
|
||||
sprintf(lineBuffer, "Brightness %d", bri);
|
||||
u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer);
|
||||
sprintf_P(lineBuffer, PSTR("Brightness %3d"), bri);
|
||||
drawString(2, 3*lineHeight, lineBuffer);
|
||||
break;
|
||||
case FLD_LINE_3_EFFECT_SPEED:
|
||||
sprintf(lineBuffer, "FX Speed %d", effectSpeed);
|
||||
u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer);
|
||||
sprintf_P(lineBuffer, PSTR("FX Speed %3d"), effectSpeed);
|
||||
drawString(2, 3*lineHeight, lineBuffer);
|
||||
break;
|
||||
case FLD_LINE_3_EFFECT_INTENSITY:
|
||||
sprintf(lineBuffer, "FX Intense %d", effectIntensity);
|
||||
u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer);
|
||||
sprintf_P(lineBuffer, PSTR("FX Intens. %3d"), effectIntensity);
|
||||
drawString(2, 3*lineHeight, lineBuffer);
|
||||
break;
|
||||
case FLD_LINE_3_PALETTE:
|
||||
default:
|
||||
showCurrentEffectOrPalette(palettes_qstrings[knownPalette], 3);
|
||||
break;
|
||||
}
|
||||
|
||||
u8x8.setFont(u8x8_font_open_iconic_arrow_1x1);
|
||||
u8x8.DRAW_GLYPH(0, markLineNum*LINE_HEIGHT, 66); // arrow icon
|
||||
|
||||
u8x8.setFont(u8x8_font_open_iconic_embedded_1x1);
|
||||
u8x8.DRAW_GLYPH(0, 0*LINE_HEIGHT, 80); // wifi icon
|
||||
u8x8.DRAW_GLYPH(0, 1*LINE_HEIGHT, 68); // home icon
|
||||
}
|
||||
|
||||
/**
|
||||
@ -300,21 +396,20 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
* TODO: palette name? This seems expensive.
|
||||
*/
|
||||
void showCurrentEffectOrPalette(char *qstring, uint8_t row) {
|
||||
uint8_t printedChars = 1;
|
||||
char lineBuffer[LINE_BUFFER_SIZE];
|
||||
|
||||
uint8_t printedChars = 0;
|
||||
char singleJsonSymbol;
|
||||
int i = 0;
|
||||
while (true) {
|
||||
singleJsonSymbol = pgm_read_byte_near(qstring + i);
|
||||
while (printedChars < getCols() - 2) {
|
||||
singleJsonSymbol = pgm_read_byte_near(qstring + printedChars);
|
||||
if (singleJsonSymbol == '"' || singleJsonSymbol == '\0' ) {
|
||||
break;
|
||||
}
|
||||
u8x8.DRAW_GLYPH(printedChars, row * LINE_HEIGHT, singleJsonSymbol);
|
||||
printedChars++;
|
||||
if ( (printedChars > u8x8.getCols() - 2)) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
lineBuffer[printedChars++] = singleJsonSymbol;
|
||||
}
|
||||
for (;printedChars<getCols()-2; printedChars++) lineBuffer[printedChars]=' ';
|
||||
lineBuffer[printedChars] = 0;
|
||||
drawString(2, row*lineHeight, lineBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -345,14 +440,9 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
}
|
||||
|
||||
// Print the overlay
|
||||
u8x8.clear();
|
||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||
if (line1) {
|
||||
u8x8.DRAW_STRING(0, 1*LINE_HEIGHT, line1);
|
||||
}
|
||||
if (line2) {
|
||||
u8x8.DRAW_STRING(0, 2*LINE_HEIGHT, line2);
|
||||
}
|
||||
clear();
|
||||
if (line1) drawString(0, 1*lineHeight, line1);
|
||||
if (line2) drawString(0, 2*lineHeight, line2);
|
||||
overlayUntil = millis() + showHowLong;
|
||||
}
|
||||
|
||||
@ -360,16 +450,16 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
* Specify what data should be defined on line 3
|
||||
* (the last line).
|
||||
*/
|
||||
void setLineThreeType(byte newLineThreeType) {
|
||||
void setLineThreeType(Line3Type newLineThreeType) {
|
||||
if (newLineThreeType == FLD_LINE_3_BRIGHTNESS ||
|
||||
newLineThreeType == FLD_LINE_3_EFFECT_SPEED ||
|
||||
newLineThreeType == FLD_LINE_3_EFFECT_INTENSITY ||
|
||||
newLineThreeType == FLD_LINE_3_PALETTE) {
|
||||
newLineThreeType == FLD_LINE_3_PALETTE ||
|
||||
newLineThreeType == FLD_LINE_3_TIME) {
|
||||
lineThreeType = newLineThreeType;
|
||||
}
|
||||
else {
|
||||
// Unknown value.
|
||||
lineThreeType = FLD_LINE_3_BRIGHTNESS;
|
||||
} else {
|
||||
// Unknown value
|
||||
lineThreeType = FLD_LINE_3_BRIGHTNESS;
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,41 +478,22 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
||||
* Below it is shown how this could be used for e.g. a light sensor
|
||||
*/
|
||||
/*
|
||||
void addToJsonInfo(JsonObject& root)
|
||||
{
|
||||
int reading = 20;
|
||||
//this code adds "u":{"Light":[20," lux"]} to the info object
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
|
||||
JsonArray lightArr = user.createNestedArray("Light"); //name
|
||||
lightArr.add(reading); //value
|
||||
lightArr.add(" lux"); //unit
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enable sleep (turn the display off) or clock mode.
|
||||
*/
|
||||
void sleepOrClock(bool enabled) {
|
||||
if (enabled) {
|
||||
if (CLOCK_MODE_ENABLED) {
|
||||
if (clockMode) {
|
||||
showTime();
|
||||
}
|
||||
else {
|
||||
u8x8.setPowerSave(1);
|
||||
setPowerSave(1);
|
||||
}
|
||||
displayTurnedOff = true;
|
||||
}
|
||||
else {
|
||||
if (!CLOCK_MODE_ENABLED) {
|
||||
u8x8.setPowerSave(0);
|
||||
if (!clockMode) {
|
||||
setPowerSave(0);
|
||||
}
|
||||
displayTurnedOff = false;
|
||||
}
|
||||
@ -433,7 +504,9 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
* on the middle rows. Based 24 or 12 hour depending on
|
||||
* the useAMPM configuration.
|
||||
*/
|
||||
void showTime() {
|
||||
void showTime(bool fullScreen = true) {
|
||||
char lineBuffer[LINE_BUFFER_SIZE];
|
||||
|
||||
updateLocalTime();
|
||||
byte minuteCurrent = minute(localTime);
|
||||
byte hourCurrent = hour(localTime);
|
||||
@ -444,12 +517,14 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
knownMinute = minuteCurrent;
|
||||
knownHour = hourCurrent;
|
||||
|
||||
u8x8.clear();
|
||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||
if (fullScreen) clear();
|
||||
|
||||
int currentMonth = month(localTime);
|
||||
sprintf(lineBuffer, "%s %d", monthShortStr(currentMonth), day(localTime));
|
||||
u8x8.DRAW_BIG_STRING(DATE_INDENT, TIME_LINE*LINE_HEIGHT, lineBuffer);
|
||||
sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(currentMonth), day(localTime));
|
||||
if (fullScreen)
|
||||
draw2x2String(DATE_INDENT, lineHeight==1 ? 0 : lineHeight, lineBuffer);
|
||||
else
|
||||
drawString(0, lineHeight, lineBuffer);
|
||||
|
||||
byte showHour = hourCurrent;
|
||||
boolean isAM = false;
|
||||
@ -467,10 +542,25 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
}
|
||||
}
|
||||
|
||||
sprintf(lineBuffer, "%02d:%02d %s", showHour, minuteCurrent, useAMPM ? (isAM ? "AM" : "PM") : "");
|
||||
sprintf_P(lineBuffer, PSTR("%02d:%02d %s"), showHour, minuteCurrent, useAMPM ? (isAM ? "AM" : "PM") : "");
|
||||
// For time, we always use LINE_HEIGHT of 2 since
|
||||
// we are printing it big.
|
||||
u8x8.DRAW_BIG_STRING(TIME_INDENT + (useAMPM ? 0 : 2), (TIME_LINE + 1) * 2, lineBuffer);
|
||||
if (fullScreen)
|
||||
draw2x2String(TIME_INDENT + (useAMPM ? 0 : 2), lineHeight*2, lineBuffer);
|
||||
else
|
||||
drawString(8, lineHeight, lineBuffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
||||
* Below it is shown how this could be used for e.g. a light sensor
|
||||
*/
|
||||
void addToJsonInfo(JsonObject& root) {
|
||||
//JsonObject user = root["u"];
|
||||
//if (user.isNull()) user = root.createNestedObject("u");
|
||||
//JsonArray data = user.createNestedArray(F("4LineDisplay"));
|
||||
//data.add(F("Loaded."));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -485,6 +575,46 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void readFromJsonState(JsonObject& root) {
|
||||
DisplayType newType = type;
|
||||
uint8_t newScl = sclPin;
|
||||
uint8_t newSda = sdaPin;
|
||||
uint8_t newContrast = contrast;
|
||||
bool newFlip = flip;
|
||||
|
||||
if (root[F("4LineDisplay_type")] != nullptr) newType = (DisplayType)root[F("4LineDisplay_type")];
|
||||
if (root[F("4LineDisplay_pin")] != nullptr) {
|
||||
newScl = (int)root[F("4LineDisplay_pin")][0];
|
||||
newSda = (int)root[F("4LineDisplay_pin")][1];
|
||||
}
|
||||
if (root[F("4LineDisplay_contrast")] != nullptr) newContrast = (int)root[F("4LineDisplay_contrast")];
|
||||
if (root[F("4LineDisplay_refreshRate")] != nullptr) refreshRate = (int)root[F("4LineDisplay_refreshRate")]*1000;
|
||||
if (root[F("4LineDisplay_screenTimeOut")] != nullptr) screenTimeout = (int)root[F("4LineDisplay_screenTimeOut")]*1000;
|
||||
if (root[F("4LineDisplay_flip")] != nullptr) {
|
||||
String str = root[F("4LineDisplay_flip")]; // checkbox -> off or on
|
||||
newFlip = (bool)(str!="off"); // off is guaranteed to be present
|
||||
}
|
||||
if (root[F("4LineDisplay_sleepMode")] != nullptr) {
|
||||
String str = root[F("4LineDisplay_sleepMode")]; // checkbox -> off or on
|
||||
sleepMode = (bool)(str!="off"); // off is guaranteed to be present
|
||||
}
|
||||
if (root[F("4LineDisplay_clockMode")] != nullptr) {
|
||||
String str = root[F("4LineDisplay_clockMode")]; // checkbox -> off or on
|
||||
clockMode = (bool)(str!="off"); // off is guaranteed to be present
|
||||
}
|
||||
|
||||
if (flip!=newFlip || contrast!=newContrast || sclPin!=newScl || sdaPin!=newSda || type!=newType) {
|
||||
if (type==SSD1306) delete (static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8));
|
||||
if (type==SH1106) delete (static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8));
|
||||
pinManager.deallocatePin(sclPin);
|
||||
pinManager.deallocatePin(sdaPin);
|
||||
sclPin = newScl;
|
||||
sdaPin = newSda;
|
||||
type = newType;
|
||||
contrast = newContrast;
|
||||
flip = newFlip;
|
||||
lineHeight = type==SH1106 ? 2 : 1;
|
||||
setup();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -502,6 +632,17 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||
*/
|
||||
void addToConfig(JsonObject& root) {
|
||||
JsonObject top = root.createNestedObject(F("4LineDisplay"));
|
||||
JsonArray i2c_pin = top.createNestedArray("pin");
|
||||
i2c_pin.add(sclPin);
|
||||
i2c_pin.add(sdaPin);
|
||||
top["type"] = type;
|
||||
top[F("flip")] = (bool) flip;
|
||||
top[F("contrast")] = contrast;
|
||||
top[F("refreshRate")] = refreshRate/1000;
|
||||
top[F("screenTimeOut")] = screenTimeout/1000;
|
||||
top[F("sleepMode")] = (bool) sleepMode;
|
||||
top[F("clockMode")] = (bool) clockMode;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -513,6 +654,17 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
|
||||
*/
|
||||
void readFromConfig(JsonObject& root) {
|
||||
JsonObject top = root[F("4LineDisplay")];
|
||||
sclPin = top["pin"][0] | FLD_PIN_SCL;
|
||||
sdaPin = top["pin"][1] | FLD_PIN_SDA;
|
||||
type = top["type"] | SSD1306;
|
||||
lineHeight = type==SH1106 ? 2 : 1;
|
||||
flip = top[F("flip")] | false ;
|
||||
contrast = top[F("contrast")] | 10;
|
||||
refreshRate = int(top[F("refreshRate")])*1000 | USER_LOOP_REFRESH_RATE_MS;
|
||||
screenTimeout = int(top[F("screenTimeOut")])*1000 | SCREEN_TIMEOUT_MS;
|
||||
sleepMode = top[F("sleepMode")] | true;
|
||||
clockMode = top[F("clockMode")] | false;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -10,6 +10,7 @@
|
||||
var pins = [6,7,8,9,10,11];
|
||||
var pinO = ["reserved","reserved","reserved","reserved","reserved","reserved"], owner;
|
||||
var loc = false, locip;
|
||||
var urows;
|
||||
function gId(s) { return d.getElementById(s); }
|
||||
function isO(i) { return (i && typeof i === 'object' && !Array.isArray(i)); }
|
||||
function H() { window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings"); }
|
||||
@ -26,7 +27,8 @@
|
||||
ldS();
|
||||
}
|
||||
function check(o,k) {
|
||||
if (o.type=="number" && o.name.substr(-4)=="_pin") {
|
||||
var n = o.name.substr(-6);
|
||||
if (o.type=="number" && n.substr(0,4)=="_pin") {
|
||||
for (var i=0; i<pins.length; i++) {
|
||||
if (k==pinO[i]) continue;
|
||||
if (o.value==pins[i]) { o.style.color="red"; break; } else o.style.color="#fff";
|
||||
@ -53,6 +55,32 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
function addField(k,f,o,a=false) {
|
||||
if (isO(o)) {
|
||||
for (const [s,v] of Object.entries(o)) {
|
||||
addField(k,s,v);
|
||||
}
|
||||
} else if (Array.isArray(o)) {
|
||||
for (var j=0; j<o.length; j++) {
|
||||
addField(k,f,o[j],true);
|
||||
}
|
||||
} else {
|
||||
var t,c;
|
||||
switch (typeof o) {
|
||||
case "boolean":
|
||||
t = "checkbox"; c = o ? `checked value="on"` : ""; break;
|
||||
case "number":
|
||||
t = "number"; c = `value="${parseInt(o,10)}"`; break;
|
||||
case "string":
|
||||
t = "text"; c = `value="${o}"`; break;
|
||||
default:
|
||||
t = "text"; c = `value="${o}"`; break;
|
||||
}
|
||||
// https://stackoverflow.com/questions/11657123/posting-both-checked-and-unchecked-checkboxes
|
||||
if (t=="checkbox") urows += `<input type="hidden" name="${k}_${f}${a?"[]":""}" value="off">`;
|
||||
urows += `${f}: <input type="${t}" name="${k}_${f}${a?"[]":""}" ${c} oninput="check(this,'${k}')"><br>`;
|
||||
}
|
||||
}
|
||||
function ldS() {
|
||||
var url = (loc?`http://${locip}`:'') + '/cfg.json';
|
||||
fetch(url, {
|
||||
@ -65,28 +93,11 @@
|
||||
.then(json => {
|
||||
umCfg = json.um;
|
||||
getPins(json);
|
||||
var urows="";
|
||||
urows="";
|
||||
if (isO(umCfg)) {
|
||||
for (const [k,o] of Object.entries(umCfg)) {
|
||||
urows += `<hr><h3>${k}</h3>`;
|
||||
if (isO(o)) {
|
||||
for (const [s,v] of Object.entries(o)) {
|
||||
var t,c;
|
||||
switch (typeof v) {
|
||||
case "boolean":
|
||||
t = "checkbox"; c = v ? `checked value="on"` : ""; break;
|
||||
case "number":
|
||||
t = "number"; c = `value="${parseInt(v,10)}"`; break;
|
||||
case "string":
|
||||
t = "text"; c = `value="${v}"`; break;
|
||||
default:
|
||||
t = "text"; c = `value="${v}"`; break;
|
||||
}
|
||||
// https://stackoverflow.com/questions/11657123/posting-both-checked-and-unchecked-checkboxes
|
||||
if (t=="checkbox") urows += `<input type="hidden" name="${k}_${s}" value="off">`;
|
||||
urows += `${s}: <input type="${t}" name="${k}_${s}" ${c} oninput="check(this,'${k}')"><br>`;
|
||||
}
|
||||
}
|
||||
addField(k,'unknown',o);
|
||||
}
|
||||
gId("um").innerHTML = urows;
|
||||
}
|
||||
|
@ -394,7 +394,7 @@ type="submit">Save & Reboot</button></form></body></html>)=====";
|
||||
// Autogenerated from wled00/data/settings_um.htm, do not edit!!
|
||||
const char PAGE_settings_um[] PROGMEM = R"=====(<!DOCTYPE html><html><head lang="en"><meta charset="utf-8"><meta
|
||||
name="viewport" content="width=500"><title>UI Settings</title><script>
|
||||
var owner,locip,d=document,umCfg={},pins=[6,7,8,9,10,11],pinO=["reserved","reserved","reserved","reserved","reserved","reserved"],loc=!1;function gId(e){return d.getElementById(e)}function isO(e){return e&&"object"==typeof e&&!Array.isArray(e)}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings")}function B(){window.open("/settings","_self")}function S(){"file:"==window.location.protocol&&(loc=!0,(locip=localStorage.getItem("locIp"))||(locip=prompt("File Mode. Please enter WLED IP!"),localStorage.setItem("locIp",locip))),ldS()}function check(e,n){if("number"==e.type&&"_pin"==e.name.substr(-4))for(var t=0;t<pins.length;t++)if(n!=pinO[t]){if(e.value==pins[t]){e.style.color="red";break}e.style.color="#fff"}}function getPins(e){if(isO(e))for(const[t,i]of Object.entries(e))if(isO(i))owner=t,getPins(i);else if("pin"==t)if(Array.isArray(i))for(var n=0;n<i.length;n++)i[n]>=0&&(pins.push(i[n]),pinO.push(owner));else i>=0&&(pins.push(i),pinO.push(owner));else if(Array.isArray(i))for(n=0;n<i.length;n++)getPins(i[n])}function ldS(){fetch((loc?"http://"+locip:"")+"/cfg.json",{method:"get"}).then(e=>(e.ok||(gId("lserr").style.display="inline"),e.json())).then(e=>{umCfg=e.um,getPins(e);var n="";if(isO(umCfg)){for(const[e,o]of Object.entries(umCfg))if(n+=`<hr><h3>${e}</h3>`,isO(o))for(const[r,s]of Object.entries(o)){var t,i;switch(typeof s){case"boolean":t="checkbox",i=s?'checked value="on"':"";break;case"number":t="number",i=`value="${parseInt(s,10)}"`;break;case"string":default:t="text",i=`value="${s}"`}"checkbox"==t&&(n+=`<input type="hidden" name="${e}_${r}" value="off">`),n+=`${r}: <input type="${t}" name="${e}_${r}" ${i} oninput="check(this,'${e}')"><br>`}gId("um").innerHTML=n}}).catch((function(e){gId("lserr").style.display="inline",console.log(e)}))}function svS(e){e.preventDefault(),console.log(d.Sf),d.Sf.checkValidity()&&d.Sf.submit()}function GetV(){}
|
||||
var owner,locip,urows,d=document,umCfg={},pins=[6,7,8,9,10,11],pinO=["reserved","reserved","reserved","reserved","reserved","reserved"],loc=!1;function gId(e){return d.getElementById(e)}function isO(e){return e&&"object"==typeof e&&!Array.isArray(e)}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings")}function B(){window.open("/settings","_self")}function S(){"file:"==window.location.protocol&&(loc=!0,(locip=localStorage.getItem("locIp"))||(locip=prompt("File Mode. Please enter WLED IP!"),localStorage.setItem("locIp",locip))),ldS()}function check(e,n){var o=e.name.substr(-6);if("number"==e.type&&"_pin"==o.substr(0,4))for(var i=0;i<pins.length;i++)if(n!=pinO[i]){if(e.value==pins[i]){e.style.color="red";break}e.style.color="#fff"}}function getPins(e){if(isO(e))for(const[o,i]of Object.entries(e))if(isO(i))owner=o,getPins(i);else if("pin"==o)if(Array.isArray(i))for(var n=0;n<i.length;n++)i[n]>=0&&(pins.push(i[n]),pinO.push(owner));else i>=0&&(pins.push(i),pinO.push(owner));else if(Array.isArray(i))for(n=0;n<i.length;n++)getPins(i[n])}function addField(e,n,o,i=!1){if(isO(o))for(const[n,i]of Object.entries(o))addField(e,n,i);else if(Array.isArray(o))for(var t=0;t<o.length;t++)addField(e,n,o[t],!0);else{var r,s;switch(typeof o){case"boolean":r="checkbox",s=o?'checked value="on"':"";break;case"number":r="number",s=`value="${parseInt(o,10)}"`;break;case"string":default:r="text",s=`value="${o}"`}"checkbox"==r&&(urows+=`<input type="hidden" name="${e}_${n}${i?"[]":""}" value="off">`),urows+=`${n}: <input type="${r}" name="${e}_${n}${i?"[]":""}" ${s} oninput="check(this,'${e}')"><br>`}}function ldS(){fetch((loc?"http://"+locip:"")+"/cfg.json",{method:"get"}).then(e=>(e.ok||(gId("lserr").style.display="inline"),e.json())).then(e=>{if(umCfg=e.um,getPins(e),urows="",isO(umCfg)){for(const[e,n]of Object.entries(umCfg))urows+=`<hr><h3>${e}</h3>`,addField(e,"unknown",n);gId("um").innerHTML=urows}}).catch((function(e){gId("lserr").style.display="inline",console.log(e)}))}function svS(e){e.preventDefault(),console.log(d.Sf),d.Sf.checkValidity()&&d.Sf.submit()}function GetV(){}
|
||||
</script>%CSS%%SCSS%</head><body onload="S()"><form
|
||||
id="form_s" name="Sf" method="post" onsubmit="svS(event)"><div class="toprow">
|
||||
<div class="helpB"><button type="button" onclick="H()">?</button></div><button
|
||||
|
@ -415,15 +415,33 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
JsonObject um = doc.createNestedObject(F("um"));
|
||||
|
||||
size_t args = request->args();
|
||||
uint j=0;
|
||||
for (size_t i=0; i<args; i++) {
|
||||
String name = request->argName(i);
|
||||
String value = request->arg(i);
|
||||
|
||||
um.remove(name); // checkboxes get two fields (first is always "off", existence of second depends on checkmark and may be "on")
|
||||
um[name] = value;
|
||||
DEBUG_PRINT(name);
|
||||
DEBUG_PRINT(" = ");
|
||||
DEBUG_PRINTLN(value);
|
||||
if (name.endsWith("[]")) {
|
||||
name.replace("[]","");
|
||||
if (!um[name].is<JsonArray>()) {
|
||||
JsonArray ar = um.createNestedArray(name);
|
||||
ar.add(value);
|
||||
j=0;
|
||||
} else {
|
||||
um[name].add(value);
|
||||
j++;
|
||||
}
|
||||
DEBUG_PRINT(name);
|
||||
DEBUG_PRINT("[");
|
||||
DEBUG_PRINT(j);
|
||||
DEBUG_PRINT("] = ");
|
||||
DEBUG_PRINTLN(value);
|
||||
} else {
|
||||
um.remove(name); // checkboxes get two fields (first is always "off", existence of second depends on checkmark and may be "on")
|
||||
um[name] = value;
|
||||
DEBUG_PRINT(name);
|
||||
DEBUG_PRINT(" = ");
|
||||
DEBUG_PRINTLN(value);
|
||||
}
|
||||
}
|
||||
usermods.readFromJsonState(um);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
// version code in format yymmddb (b = daily build)
|
||||
#define VERSION 2104081
|
||||
#define VERSION 2104111
|
||||
|
||||
//uncomment this if you have a "my_config.h" file you'd like to use
|
||||
//#define WLED_USE_MY_CONFIG
|
||||
|
@ -322,6 +322,7 @@ String settingsProcessor(const String& var)
|
||||
{
|
||||
if (var == "CSS") {
|
||||
char buf[2048];
|
||||
buf[0] = 0;
|
||||
getSettingsJS(optionType, buf);
|
||||
return String(buf);
|
||||
}
|
||||
|
@ -268,15 +268,20 @@ void getSettingsJS(byte subPage, char* dest)
|
||||
if (!mods.isNull()) {
|
||||
uint8_t i=0;
|
||||
for (JsonPair kv : mods) {
|
||||
if (strncmp_P(kv.key().c_str(),PSTR("pin_"),4) == 0) {
|
||||
if (i++) oappend(SET_F(","));
|
||||
oappendi((int)kv.value());
|
||||
} else if (!kv.value().isNull()) {
|
||||
if (!kv.value().isNull()) {
|
||||
// element is an JsonObject
|
||||
JsonObject obj = kv.value();
|
||||
if (obj["pin"] != nullptr) {
|
||||
if (i++) oappend(SET_F(","));
|
||||
oappendi((int)obj["pin"]);
|
||||
if (obj["pin"].is<JsonArray>()) {
|
||||
JsonArray pins = obj["pin"].as<JsonArray>();
|
||||
for (JsonVariant pv : pins) {
|
||||
if (i++) oappend(SET_F(","));
|
||||
oappendi(pv.as<int>());
|
||||
}
|
||||
} else {
|
||||
if (i++) oappend(SET_F(","));
|
||||
oappendi((int)obj["pin"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user