Change GUI ajax updating buttons/sliders/shutters

This commit is contained in:
Theo Arends 2024-11-10 11:46:52 +01:00
parent a5c33eba5e
commit 7e3f093dc9
4 changed files with 343 additions and 141 deletions

View File

@ -267,12 +267,14 @@ const char HTTP_HEAD_STYLE3[] PROGMEM =
"<h2>%s</h2>"; // Device name
const char HTTP_MSG_SLIDER_GRADIENT[] PROGMEM =
"<td colspan='%d' style='width:%d%%'>"
"<div id='%s' class='r' style='background-image:linear-gradient(to right,%s,%s);'>"
"<input id='sl%d' type='range' min='%d' max='%d' value='%d' onchange='lc(\"%c\",%d,value)'>"
"</div>";
"</div>"
"</td>";
const char HTTP_MSG_SLIDER_UPDATE[] PROGMEM =
"<img style='display:none;' src onerror=";
const char HTTP_MSG_EXEC_JAVASCRIPT[] PROGMEM =
"<img style='display:none;' src onerror=\"";
const char HTTP_MSG_RSTRT[] PROGMEM =
"<br><div style='text-align:center;'>" D_DEVICE_WILL_RESTART "</div><br>";
@ -416,7 +418,10 @@ const char HTTP_END[] PROGMEM =
"</body>"
"</html>";
const char HTTP_DEVICE_CONTROL[] PROGMEM = "<td style='width:%d%%'><button onclick='la(\"&o=%d\");'>%s%s</button></td>"; // ?o is related to WebGetArg(PSTR("o"), tmp, sizeof(tmp))
//const char HTTP_DEVICE_CONTROL[] PROGMEM = "<td style='width:%d%%'><button onclick='la(\"&o=%d\");'>%s%s</button></td>"; // ?o is related to WebGetArg(PSTR("o"), tmp, sizeof(tmp))
//const char HTTP_DEVICE_CONTROL[] PROGMEM = "<td style='width:%d%%'><button id='o%d' style='background:#%06x;' onclick='la(\"&o=%d\");'>%s%s</button></td>"; // ?o is related to WebGetArg(PSTR("o"), tmp, sizeof(tmp))
const char HTTP_DEVICE_CONTROL[] PROGMEM = "<td style='width:%d%%'><button id='o%d' onclick='la(\"&o=%d\");'>%s%s</button></td>"; // ?o is related to WebGetArg(PSTR("o"), tmp, sizeof(tmp))
const char HTTP_DEVICE_STATE[] PROGMEM = "<td style='width:%d%%;text-align:center;font-weight:%s;font-size:%dpx'>%s</td>";
enum ButtonTitle {
@ -464,6 +469,7 @@ struct WEB {
uint32_t upload_size = 0;
int slider[LST_MAX];
uint16_t upload_error = 0;
uint8_t slider_update[LST_MAX];
uint8_t state = HTTP_OFF;
uint8_t upload_file_type;
uint8_t config_block_count = 0;
@ -602,6 +608,7 @@ void StartWebserver(int type)
for (uint32_t i = 0; i < LST_MAX; i++) {
Web.slider[i] = -1;
Web.slider_update[i] = 0;
}
if (!Webserver) {
@ -1150,31 +1157,30 @@ uint32_t WebUseManagementSubmenu(void) {
return management_count -1;
}
uint32_t WebDeviceColumns(void) {
const uint32_t max_columns = 8;
uint32_t rows = TasmotaGlobal.devices_present / max_columns;
if (TasmotaGlobal.devices_present % max_columns) { rows++; }
uint32_t cols = TasmotaGlobal.devices_present / rows;
if (TasmotaGlobal.devices_present % rows) { cols++; }
return cols;
}
#ifdef USE_LIGHT
void WebSliderColdWarm(void) {
Web.slider[0] = LightGetColorTemp();
WSContentSend_P(PSTR("<tr>"));
WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, // Cold Warm
2, 100,
PSTR("a"), // a - Unique HTML id
PSTR("#eff"), PSTR("#f81"), // 6500k in RGB (White) to 2500k in RGB (Warm Yellow)
1, // sl1 - used for slider updates
153, 500, // Range color temperature
Web.slider[0],
't', 0); // t0 - Value id releated to lc("t0", value) and WebGetArg("t0", tmp, sizeof(tmp));
WSContentSend_P(PSTR("</tr>"));
}
#endif // USE_LIGHT
void HandleRoot(void)
{
const char HTTP_MSG_SLIDER_SHUTTERT[] PROGMEM =
"<td style='width:70%%'>"
"<div style='padding:0px 2px;text-align:center;font-size:12px;'><span>%s</span>"
"<input id='s27%d' type='range' min='0' max='100' value='%d' onchange='lc(\"u\",%d,value)'>"
"</div>"
"</td>";
void HandleRoot(void) {
#ifndef NO_CAPTIVE_PORTAL
if (CaptivePortal()) { return; } // If captive portal redirect instead of displaying the page.
#endif // NO_CAPTIVE_PORTAL
@ -1206,6 +1212,18 @@ void HandleRoot(void)
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_MAIN_MENU));
/*
Display GUI with items in following order:
- Header with module name and device name
- Dynamic ajax update region
- Optional power toggle buttons for relays, display and iFan with feedback
- Optional shutter buttons and slider with feedback
- Optional light button and slider(s) with feedback
- Call FUNC_WEB_ADD_MAIN_BUTTON
- Optional buttons and sliders with feedback
- Show default main buttons (Configuration, Information, Firmware Upgrade, Tools and Restart)
*/
char stemp[33];
WSContentStart_P(PSTR(D_MAIN_MENU));
@ -1215,16 +1233,129 @@ void HandleRoot(void)
WSContentSend_P(HTTP_SCRIPT_ROOT, Settings->web_refresh);
#endif
WSContentSend_P(HTTP_SCRIPT_ROOT_PART2);
WSContentSendStyle();
WSContentSend_P(PSTR("<div style='padding:0;' id='l1' name='l1'></div>"));
if (TasmotaGlobal.devices_present) {
uint32_t buttons_non_light = TasmotaGlobal.devices_present;
uint32_t button_idx = 1;
#ifdef USE_LIGHT
// Chk for reduced toggle buttons used by lights
if (TasmotaGlobal.light_type) {
// Find and skip light buttons (Lights are controlled by the last TasmotaGlobal.devices_present (or 2))
buttons_non_light = LightDevice() -1;
}
#endif // USE_LIGHT
uint32_t buttons_non_light_non_shutter = buttons_non_light;
#ifdef USE_SHUTTER
// Chk for reduced toggle buttons used by shutters
uint32_t shutter_button = 0; // Bitmask for each button
// Find and skip dedicated shutter buttons
if (buttons_non_light && Settings->flag3.shutter_mode) { // SetOption80 - Enable shutter support
for (button_idx = 1; button_idx <= buttons_non_light; button_idx++) {
if (IsShutterWebButton(button_idx) != 0) {
buttons_non_light_non_shutter--;
shutter_button |= (1 << (button_idx -1)); // Set button bit in bitmask
}
}
}
#endif // USE_SHUTTER
if (buttons_non_light_non_shutter) { // Any non light AND non shutter button
// Display toggle buttons
WSContentSend_P(HTTP_TABLE100); // "<table style='width:100%%'>"
WSContentSend_P(PSTR("<tr>"));
#ifdef USE_SONOFF_IFAN
if (IsModuleIfan()) {
WSContentSend_P(HTTP_DEVICE_CONTROL, 36, 1, 1,
(strlen(SettingsText(SET_BUTTON1))) ? SettingsTextEscaped(SET_BUTTON1).c_str() : PSTR(D_BUTTON_TOGGLE),
"");
for (uint32_t i = 0; i < MaxFanspeed(); i++) {
snprintf_P(stemp, sizeof(stemp), PSTR("%d"), i);
WSContentSend_P(HTTP_DEVICE_CONTROL, 16, i +2, i +2,
(strlen(SettingsText(SET_BUTTON2 + i))) ? SettingsTextEscaped(SET_BUTTON2 + i).c_str() : stemp,
"");
}
} else {
#endif // USE_SONOFF_IFAN
const uint32_t max_columns = 8;
uint32_t rows = buttons_non_light_non_shutter / max_columns;
if (buttons_non_light_non_shutter % max_columns) { rows++; }
uint32_t cols = buttons_non_light_non_shutter / rows;
if (buttons_non_light_non_shutter % rows) { cols++; }
uint32_t button_ptr = 0;
for (button_idx = 1; button_idx <= buttons_non_light; button_idx++) {
#ifdef USE_SHUTTER
if (bitRead(shutter_button, button_idx -1)) { continue; } // Skip non-sequential shutter button
#endif // USE_SHUTTER
bool set_button = ((button_idx <= MAX_BUTTON_TEXT) && strlen(GetWebButton(button_idx -1)));
snprintf_P(stemp, sizeof(stemp), PSTR(" %d"), button_idx);
WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / cols, button_idx, button_idx,
(set_button) ? HtmlEscape(GetWebButton(button_idx -1)).c_str() : (cols < 5) ? PSTR(D_BUTTON_TOGGLE) : "",
(set_button) ? "" : (TasmotaGlobal.devices_present > 1) ? stemp : "");
button_ptr++;
if (0 == button_ptr % cols) { WSContentSend_P(PSTR("</tr><tr>")); }
}
#ifdef USE_SONOFF_IFAN
}
#endif // USE_SONOFF_IFAN
WSContentSend_P(PSTR("</tr></table>"));
}
#ifdef USE_SHUTTER
if (shutter_button) { // Any button bit set
WSContentSend_P(HTTP_TABLE100); // "<table style='width:100%%'>"
int32_t ShutterWebButton;
uint32_t shutter_button_idx = 1;
for (uint32_t shutter_idx = 0; shutter_idx < TasmotaGlobal.shutters_present ; shutter_idx++) {
while ((0 == shutter_button & (1 << (shutter_button_idx -1)))) { shutter_button_idx++; }
WSContentSend_P(PSTR("<tr>"));
shutter_button_idx++; // Left button is next button first (down)
for (uint32_t j = 0; j < 2; j++) {
ShutterWebButton = IsShutterWebButton(shutter_button_idx);
WSContentSend_P(HTTP_DEVICE_CONTROL, 15, shutter_button_idx, shutter_button_idx,
((ShutterGetOptions(abs(ShutterWebButton)-1) & 2) /* is locked */ ? "-" :
((ShutterGetOptions(abs(ShutterWebButton)-1) & 8) /* invert web buttons */ ? ((ShutterWebButton>0) ? "&#9660;" : "&#9650;") : ((ShutterWebButton>0) ? "&#9650;" : "&#9660;"))),
"");
if (1 == j) { break; }
shutter_button_idx--; // Right button is previous button (up)
bool set_button = ((shutter_button_idx <= MAX_BUTTON_TEXT) && strlen(GetWebButton(shutter_button_idx -1)));
snprintf_P(stemp, sizeof(stemp), PSTR("Shutter %d"), shutter_idx +1);
WSContentSend_P(HTTP_MSG_SLIDER_SHUTTERT,
(set_button) ? HtmlEscape(GetWebButton(shutter_button_idx -1)).c_str() : stemp,
shutter_idx +1,
(ShutterGetOptions(shutter_idx) & 1) ? (100 - ShutterRealToPercentPosition(-9999, shutter_idx)) : ShutterRealToPercentPosition(-9999, shutter_idx),
shutter_idx +1);
}
WSContentSend_P(PSTR("</tr>"));
shutter_button_idx += 2;
}
WSContentSend_P(PSTR("</table>"));
}
#endif // USE_SHUTTER
#ifdef USE_LIGHT
if (TasmotaGlobal.light_type) {
WSContentSend_P(HTTP_TABLE100); // "<table style='width:100%%'>"
uint8_t light_subtype = TasmotaGlobal.light_type &7;
if (!Settings->flag3.pwm_multi_channels) { // SetOption68 0 - Enable multi-channels PWM instead of Color PWM
bool split_white = ((LST_RGBW <= light_subtype) && (TasmotaGlobal.devices_present > 1)); // Only on RGBW or RGBCW and SetOption37 128
bool split_white = ((LST_RGBW <= light_subtype) && (TasmotaGlobal.devices_present > 1) && (Settings->param[P_RGB_REMAP] & 128)); // Only on RGBW or RGBCW and SetOption37 128
if ((LST_COLDWARM == light_subtype) || ((LST_RGBCW == light_subtype) && !split_white)) {
WebSliderColdWarm();
@ -1236,13 +1367,16 @@ void HandleRoot(void)
LightGetHSB(&hue, &sat, nullptr);
Web.slider[1] = hue;
WSContentSend_P(PSTR("<tr>"));
WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, // Hue
2, 100,
PSTR("b"), // b - Unique HTML id
PSTR("#800"), PSTR("#f00 5%,#ff0 20%,#0f0 35%,#0ff 50%,#00f 65%,#f0f 80%,#f00 95%,#800"), // Hue colors
2, // sl2 - Unique range HTML id - Used as source for Saturation end color and slider updates
0, 359, // Range valid Hue
Web.slider[1],
'h', 0); // h0 - Value id
WSContentSend_P(PSTR("</tr>"));
uint8_t dcolor = changeUIntScale(Settings->light_dimmer, 0, 100, 0, 255);
char scolor[8];
@ -1252,95 +1386,136 @@ void HandleRoot(void)
snprintf_P(stemp, sizeof(stemp), PSTR("#%02X%02X%02X"), red, green, blue); // Saturation end color
Web.slider[2] = changeUIntScale(sat, 0, 255, 0, 100);
WSContentSend_P(PSTR("<tr>"));
WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, // Saturation
2, 100,
PSTR("s"), // s - Unique HTML id related to eb('s').style.background='linear-gradient(to right,rgb('+sl+'%%,'+sl+'%%,'+sl+'%%),hsl('+eb('sl2').value+',100%%,50%%))';
scolor, stemp, // Brightness to max current color
3, // sl3 - Unique range HTML id - Used for slider updates
0, 100, // Range 0 to 100%
Web.slider[2],
'n', 0); // n0 - Value id
WSContentSend_P(PSTR("</tr>"));
}
bool set_button = ((button_idx <= MAX_BUTTON_TEXT) && strlen(GetWebButton(button_idx -1)));
char first[2];
snprintf_P(first, sizeof(first), PSTR("%s"), PSTR(D_BUTTON_TOGGLE));
char butt_txt[4];
snprintf_P(butt_txt, sizeof(butt_txt), PSTR("%s"), (set_button) ? HtmlEscape(GetWebButton(button_idx -1)).c_str() : first);
char number[8];
WSContentSend_P(PSTR("<tr>"));
WSContentSend_P(HTTP_DEVICE_CONTROL, 15, button_idx, button_idx,
butt_txt,
(set_button) ? "" : itoa(button_idx, number, 10));
button_idx++;
Web.slider[3] = Settings->light_dimmer;
WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, // Brightness - Black to White
1, 85,
PSTR("c"), // c - Unique HTML id
PSTR("#000"), PSTR("#fff"), // Black to White
4, // sl4 - Unique range HTML id - Used as source for Saturation begin color and slider updates
Settings->flag3.slider_dimmer_stay_on, 100, // Range 0/1 to 100% (SetOption77 - Do not power off if slider moved to far left)
Web.slider[3],
'd', 0); // d0 - Value id is related to lc("d0", value) and WebGetArg("d0", tmp, sizeof(tmp));
WSContentSend_P(PSTR("</tr>"));
if (split_white) { // SetOption37 128
if (LST_RGBCW == light_subtype) {
WebSliderColdWarm();
}
uint32_t width = 100;
WSContentSend_P(PSTR("<tr>"));
if (button_idx <= TasmotaGlobal.devices_present) {
bool set_button = ((button_idx <= MAX_BUTTON_TEXT) && strlen(GetWebButton(button_idx -1)));
char first[2];
snprintf_P(first, sizeof(first), PSTR("%s"), PSTR(D_BUTTON_TOGGLE));
char butt_txt[4];
snprintf_P(butt_txt, sizeof(butt_txt), PSTR("%s"), (set_button) ? HtmlEscape(GetWebButton(button_idx -1)).c_str() : first);
char number[8];
WSContentSend_P(HTTP_DEVICE_CONTROL, 15, button_idx, button_idx,
butt_txt,
(set_button) ? "" : itoa(button_idx, number, 10));
button_idx++;
width = 85;
}
Web.slider[4] = LightGetDimmer(2);
WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, // White brightness - Black to White
(100 == width) ? 2 : 1, width,
PSTR("f"), // f - Unique HTML id
PSTR("#000"), PSTR("#fff"), // Black to White
5, // sl5 - Unique range HTML id - Used for slider updates
Settings->flag3.slider_dimmer_stay_on, 100, // Range 0/1 to 100% (SetOption77 - Do not power off if slider moved to far left)
Web.slider[4],
'w', 0); // w0 - Value id is related to lc("w0", value) and WebGetArg("w0", tmp, sizeof(tmp));
WSContentSend_P(PSTR("</tr>"));
}
} else { // Settings->flag3.pwm_multi_channels - SetOption68 1 - Enable multi-channels PWM instead of Color PWM
uint32_t pwm_channels = light_subtype > LST_MAX ? LST_MAX : light_subtype;
uint32_t pwm_channels = TasmotaGlobal.devices_present - buttons_non_light;
stemp[0] = 'e'; stemp[1] = '0'; stemp[2] = '\0'; // d0
for (uint32_t i = 0; i < pwm_channels; i++) {
stemp[1]++; // e1 to e5 - Make unique ids
bool set_button = ((button_idx <= MAX_BUTTON_TEXT) && strlen(GetWebButton(button_idx -1)));
char first[2];
snprintf_P(first, sizeof(first), PSTR("%s"), PSTR(D_BUTTON_TOGGLE));
char butt_txt[4];
snprintf_P(butt_txt, sizeof(butt_txt), PSTR("%s"),
(set_button) ? HtmlEscape(GetWebButton(button_idx -1)).c_str() : first);
char number[8];
WSContentSend_P(PSTR("<tr>"));
WSContentSend_P(HTTP_DEVICE_CONTROL, 15, button_idx, button_idx,
butt_txt,
(set_button) ? "" : itoa(button_idx, number, 10));
button_idx++;
stemp[1]++; // e1 to e5 - Make unique ids
Web.slider[i] = changeUIntScale(Settings->light_color[i], 0, 255, 0, 100);
WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, // Channel brightness - Black to White
1, 85,
stemp, // e1 to e5 - Unique HTML id
PSTR("#000"), PSTR("#fff"), // Black to White
i+1, // sl1 to sl5 - Unique range HTML id - Used for slider updates
1, 100, // Range 1 to 100%
Web.slider[i],
'e', i+1); // e1 to e5 - Value id
WSContentSend_P(PSTR("</tr>"));
}
} // Settings->flag3.pwm_multi_channels
WSContentSend_P(PSTR("</table>"));
}
#endif // USE_LIGHT
WSContentSend_P(HTTP_TABLE100);
WSContentSend_P(PSTR("<tr>"));
}
// Init buttons
WSContentSend_P(PSTR("<script>"));
uint32_t max_devices = TasmotaGlobal.devices_present;
#ifdef USE_SONOFF_IFAN
if (IsModuleIfan()) {
WSContentSend_P(HTTP_DEVICE_CONTROL, 36, 1,
(strlen(SettingsText(SET_BUTTON1))) ? SettingsTextEscaped(SET_BUTTON1).c_str() : PSTR(D_BUTTON_TOGGLE),
"");
for (uint32_t i = 0; i < MaxFanspeed(); i++) {
snprintf_P(stemp, sizeof(stemp), PSTR("%d"), i);
WSContentSend_P(HTTP_DEVICE_CONTROL, 16, i +2,
(strlen(SettingsText(SET_BUTTON2 + i))) ? SettingsTextEscaped(SET_BUTTON2 + i).c_str() : stemp,
"");
max_devices = MaxFanspeed() +1; // 4 -> 5
}
} else {
#endif // USE_SONOFF_IFAN
uint32_t cols = WebDeviceColumns();
for (uint32_t idx = 1; idx <= TasmotaGlobal.devices_present; idx++) {
bool set_button = ((idx <= MAX_BUTTON_TEXT) && strlen(GetWebButton(idx -1)));
#ifdef USE_SHUTTER
int32_t ShutterWebButton;
if (ShutterWebButton = IsShutterWebButton(idx)) {
WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / cols, idx,
(set_button) ? HtmlEscape(GetWebButton(idx -1)).c_str() : ((ShutterGetOptions(abs(ShutterWebButton)-1) & 2) /* is locked */ ? "-" : ((ShutterGetOptions(abs(ShutterWebButton)-1) & 8) /* invert web buttons */ ? ((ShutterWebButton>0) ? "&#9660;" : "&#9650;") : ((ShutterWebButton>0) ? "&#9650;" : "&#9660;"))),
"");
} else {
#endif // USE_SHUTTER
snprintf_P(stemp, sizeof(stemp), PSTR(" %d"), idx);
WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / cols, idx,
(set_button) ? HtmlEscape(GetWebButton(idx -1)).c_str() : (cols < 5) ? PSTR(D_BUTTON_TOGGLE) : "",
(set_button) ? "" : (TasmotaGlobal.devices_present > 1) ? stemp : "");
#ifdef USE_SHUTTER
}
#endif // USE_SHUTTER
if (0 == idx % cols) { WSContentSend_P(PSTR("</tr><tr>")); }
}
for (uint32_t idx = 1; idx <= max_devices; idx++) {
bool not_active = !bitRead(TasmotaGlobal.power, idx -1);
#ifdef USE_SONOFF_IFAN
if (IsModuleIfan() && (idx > 1)) {
not_active = true;
}
#endif // USE_SONOFF_IFAN
WSContentSend_P(PSTR("</tr></table>"));
if (not_active) {
WSContentSend_P(PSTR("eb('o%d').style.background='#%06x';"), idx, WebColor(COL_FORM));
}
}
WSContentSend_P(PSTR("</script>"));
#ifndef FIRMWARE_MINIMAL
XdrvXsnsCall(FUNC_WEB_ADD_MAIN_BUTTON);
@ -1507,8 +1682,48 @@ bool HandleRootStatusRefresh(void)
WSContentBegin(200, CT_HTML);
#endif // USE_WEB_SSE
bool msg_exec_javascript = false;
if (TasmotaGlobal.devices_present) {
// Update changed web buttons
uint32_t max_devices = TasmotaGlobal.devices_present;
#ifdef USE_SONOFF_IFAN
uint32_t fanspeed;
if (IsModuleIfan()) {
// Single power relay and four virtual buttons
max_devices = MaxFanspeed() +1; // 4 -> 5
fanspeed = GetFanspeed() +2; // 0..3 -> 2..5
}
#endif // USE_SONOFF_IFAN
WSContentSend_P(HTTP_MSG_EXEC_JAVASCRIPT); // "<img style='display:none;' src onerror=\""
msg_exec_javascript = true;
for (uint32_t idx = 1; idx <= max_devices; idx++) {
bool active = bitRead(TasmotaGlobal.power, idx -1);
#ifdef USE_SONOFF_IFAN
if (IsModuleIfan() && (idx > 1)) {
active = (fanspeed == idx);
}
#endif // USE_SONOFF_IFAN
WSContentSend_P(PSTR("eb('o%d').style.background='#%06x';"),
idx, WebColor((active) ? COL_BUTTON : COL_FORM));
}
}
#ifdef USE_SHUTTER
if (!msg_exec_javascript) {
WSContentSend_P(HTTP_MSG_EXEC_JAVASCRIPT); // "<img style='display:none;' src onerror=\""
msg_exec_javascript = true;
}
for (uint32_t i = 0; i < TasmotaGlobal.shutters_present; i++) {
WSContentSend_P(PSTR("eb('s27%d').value='%d';"), i +1,
(ShutterGetOptions(i) & 1) ? (100 - ShutterRealToPercentPosition(-9999, i)) : ShutterRealToPercentPosition(-9999, i));
}
#endif // USE_SHUTTER
#ifdef USE_LIGHT
if (!Settings->flag6.disable_slider_updates) { // SetOption161 0 - (Light) Disable slider updates (1)
uint16_t hue;
uint8_t sat;
int current_value = -1;
@ -1535,16 +1750,26 @@ bool HandleRootStatusRefresh(void)
current_value = changeUIntScale(Settings->light_color[i], 0, 255, 0, 100);
}
if (current_value != Web.slider[i]) {
Web.slider_update[i]++;
if (Web.slider_update[i] > 2) { // Allow two other users screen sync
Web.slider_update[i] = 0;
Web.slider[i] = current_value;
// https://stackoverflow.com/questions/4057236/how-to-add-onload-event-to-a-div-element
WSContentSend_P(HTTP_MSG_SLIDER_UPDATE); // "<img style='display:none;' src onerror="
WSContentSend_P(PSTR("\"eb('sl%d').value='%d';\">"), i +1, current_value);
}
// https://stackoverflow.com/questions/4057236/how-to-add-onload-event-to-a-div-element
if (!msg_exec_javascript) {
WSContentSend_P(HTTP_MSG_EXEC_JAVASCRIPT); // "<img style='display:none;' src onerror=\""
msg_exec_javascript = true;
}
WSContentSend_P(PSTR("eb('sl%d').value='%d';"), i +1, current_value);
}
}
}
#endif // USE_LIGHT
if (msg_exec_javascript) {
WSContentSend_P(PSTR("\">"));
}
WSContentSend_P(PSTR("{t}")); // <table style='width:100%'>
WSContentSeparator(3); // Reset seperator to ignore previous outputs
if (Settings->web_time_end) {
@ -1554,30 +1779,6 @@ bool HandleRootStatusRefresh(void)
XsnsXdrvCall(FUNC_WEB_SENSOR);
WSContentSend_P(PSTR("</table>"));
if (TasmotaGlobal.devices_present) {
WSContentSend_P(PSTR("{t}<tr>"));
#ifdef USE_SONOFF_IFAN
if (IsModuleIfan()) {
WSContentSend_P(HTTP_DEVICE_STATE, 36, (bitRead(TasmotaGlobal.power, 0)) ? PSTR("bold") : PSTR("normal"), 54, GetStateText(bitRead(TasmotaGlobal.power, 0)));
uint32_t fanspeed = GetFanspeed();
snprintf_P(svalue, sizeof(svalue), PSTR("%d"), fanspeed);
WSContentSend_P(HTTP_DEVICE_STATE, 64, (fanspeed) ? PSTR("bold") : PSTR("normal"), 54, (fanspeed) ? svalue : GetStateText(0));
} else {
#endif // USE_SONOFF_IFAN
uint32_t cols = WebDeviceColumns();
uint32_t fontsize = (cols < 5) ? 70 - (cols * 8) : 32;
for (uint32_t idx = 1; idx <= TasmotaGlobal.devices_present; idx++) {
snprintf_P(svalue, sizeof(svalue), PSTR("%d"), bitRead(TasmotaGlobal.power, idx -1));
WSContentSend_P(HTTP_DEVICE_STATE, 100 / cols, (bitRead(TasmotaGlobal.power, idx -1)) ? PSTR("bold") : PSTR("normal"), fontsize,
(cols < 5) ? GetStateText(bitRead(TasmotaGlobal.power, idx -1)) : svalue);
if (0 == idx % cols) { WSContentSend_P(PSTR("</tr><tr>")); }
}
#ifdef USE_SONOFF_IFAN
}
#endif // USE_SONOFF_IFAN
WSContentSend_P(PSTR("</tr></table>"));
}
WSContentSend_P(PSTR("\n\n")); // Prep for SSE
WSContentEnd();
@ -1754,12 +1955,12 @@ void HandleTemplateConfiguration(void) {
WSContentSendStyle();
WSContentSend_P(HTTP_FORM_TEMPLATE);
WSContentSend_P(HTTP_TABLE100);
WSContentSend_P(HTTP_TABLE100); // "<table style='width:100%%'>"
WSContentSend_P(PSTR("<tr><td><b>" D_TEMPLATE_NAME "</b></td><td style='width:200px'><input id='s1' placeholder='" D_TEMPLATE_NAME "'></td></tr>"
"<tr><td><b>" D_BASE_TYPE "</b></td><td><select id='g99' onchange='st(this.value)'></select></td></tr>"
"</table>"
"<hr/>"));
WSContentSend_P(HTTP_TABLE100);
WSContentSend_P(HTTP_TABLE100); // "<table style='width:100%%'>"
for (uint32_t i = 0; i < MAX_GPIO_PIN; i++) {
#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6
// ESP32C2/C3/C6 all gpios are in the template, flash are hidden

View File

@ -2396,7 +2396,7 @@ bool Xdrv27(uint32_t function)
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
ShutterShow();
// ShutterShow();
break;
#endif // USE_WEBSERVER
case FUNC_ACTIVE:

View File

@ -1978,7 +1978,7 @@ bool Xdrv27(uint32_t function)
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
ShutterShow();
// ShutterShow();
break;
#endif // USE_WEBSERVER
case FUNC_ACTIVE:

View File

@ -300,11 +300,11 @@ struct DALI {
uint8_t last_dimmer;
uint8_t dimmer[DALI_MAX_STORED];
uint8_t web_dimmer[DALI_MAX_STORED];
uint8_t web_update[DALI_MAX_STORED];
uint8_t target;
bool allow_light;
bool last_power;
bool power[DALI_MAX_STORED];
bool web_power[DALI_MAX_STORED];
bool available;
bool response;
bool light_sync;
@ -783,7 +783,7 @@ uint32_t DaliCommission(uint8_t init_arg) {
#ifdef USE_LIGHT
DaliInitLight();
uint32_t address = (Settings->sbflag1.dali_light) ? DaliTarget2Address() : DALI_BROADCAST_DP;
uint32_t address = (Settings->sbflag1.dali_light) ? DaliTarget2Address() : DALI_BROADCAST_DP; // DaliLight 1
DaliSendData(address, Dali->power[0]); // Restore lights
#else
DaliSendData(DALI_BROADCAST_DP, Dali->power[0]); // Restore lights
@ -912,8 +912,8 @@ bool DaliInit(uint32_t function) {
Dali->allow_light = (FUNC_MODULE_INIT == function); // Light control is possible
AddLog(LOG_LEVEL_INFO, PSTR("DLI: GPIO%d(RX%s) and GPIO%d(TX%s)"),
Dali->pin_rx, (Dali->invert_rx)?"i":"", Dali->pin_tx, (Dali->invert_tx)?"i":"");
AddLog(LOG_LEVEL_INFO, PSTR("DLI: GPIO%d(RX%s) and GPIO%d(TX%s)%s"),
Dali->pin_rx, (Dali->invert_rx)?"i":"", Dali->pin_tx, (Dali->invert_tx)?"i":"", (Dali->allow_light)?" as light":"");
pinMode(Dali->pin_tx, OUTPUT);
digitalWrite(Dali->pin_tx, (Dali->invert_tx) ? LOW : HIGH); // Idle
@ -1221,10 +1221,10 @@ void CmndDaliLight(void) {
// DaliLight 0 - Disable light controls
// DaliLight 1 - Enable light controls
if (Dali->allow_light && (XdrvMailbox.data_len > 0)) {
Settings->sbflag1.dali_light = XdrvMailbox.payload &1;
Settings->sbflag1.dali_light = XdrvMailbox.payload &1; // DaliLight 0/1
TasmotaGlobal.restart_flag = 2; // Restart to update GUI
}
ResponseCmndStateText(Settings->sbflag1.dali_light);
ResponseCmndStateText(Settings->sbflag1.dali_light); // DaliLight 0/1
}
#endif // USE_LIGHT
@ -1243,12 +1243,11 @@ const char HTTP_MSG_SLIDER_DALI[] PROGMEM =
void DaliWebAddMainSlider(void) {
WSContentSend_P(HTTP_TABLE100);
char number[12];
for (uint32_t i = Settings->sbflag1.dali_light; i <= Settings->mbflag2.dali_group_sliders; i++) {
for (uint32_t i = Settings->sbflag1.dali_light; i <= Settings->mbflag2.dali_group_sliders; i++) { // DaliLight 0/1, DaliGroupSliders
Dali->web_dimmer[i] = Dali->dimmer[i];
Dali->web_power[i] = Dali->power[i];
WSContentSend_P(HTTP_MSG_SLIDER_DALI, // Brightness - Black to White
i, // k75<i>
WebColor((Dali->web_power[i])?COL_BUTTON:COL_BACKGROUND),
WebColor((Dali->power[i]) ? COL_BUTTON : COL_FORM),
i, // k75=<i>
(0==i)?"B":"G", // B (Broadcast) or G1 to G16 (Group)
(0==i)?"":itoa(i, number, 10),
@ -1266,7 +1265,7 @@ void DaliWebGetArg(void) {
char webindex[8]; // WebGetArg name
uint32_t index;
for (uint32_t i = Settings->sbflag1.dali_light; i <= Settings->mbflag2.dali_group_sliders; i++) {
for (uint32_t i = Settings->sbflag1.dali_light; i <= Settings->mbflag2.dali_group_sliders; i++) { // DaliLight 0/1, DaliGroupSliders
snprintf_P(webindex, sizeof(webindex), PSTR("i75%d"), i);
WebGetArg(webindex, tmp, sizeof(tmp)); // 0 - 100 percent
if (strlen(tmp)) {
@ -1292,22 +1291,24 @@ void DaliShow(bool json) {
ResponseAppendDali(0);
#ifdef USE_WEBSERVER
} else {
for (uint32_t i = Settings->sbflag1.dali_light; i <= Settings->mbflag2.dali_group_sliders; i++) {
if (Dali->power[i] != Dali->web_power[i]) {
Dali->web_power[i] = Dali->power[i];
WSContentSend_P(HTTP_MSG_SLIDER_UPDATE); // "<img style='display:none;' src onerror="
WSContentSend_P(PSTR("\"eb('k75%d').style='background:#%06x;';\">"),
i, WebColor((Dali->web_power[i])?COL_BUTTON:COL_BACKGROUND));
WSContentSeparator(3); // Don't print separator on next WSContentSeparator(1)
}
WSContentSend_P(PSTR("</table>")); // Terminate current {t}
WSContentSend_P(HTTP_MSG_EXEC_JAVASCRIPT); // "<img style='display:none;' src onerror=\""
for (uint32_t i = Settings->sbflag1.dali_light; i <= Settings->mbflag2.dali_group_sliders; i++) { // DaliLight 0/1, DaliGroupSliders
WSContentSend_P(PSTR("eb('k75%d').style='background:#%06x';"),
i, WebColor((Dali->power[i]) ? COL_BUTTON : COL_FORM));
if (Dali->dimmer[i] != Dali->web_dimmer[i]) {
Dali->web_update[i]++;
if (Dali->web_update[i] > 2) { // Allow two other users screen sync
Dali->web_update[i] = 0;
Dali->web_dimmer[i] = Dali->dimmer[i];
WSContentSend_P(HTTP_MSG_SLIDER_UPDATE); // "<img style='display:none;' src onerror="
WSContentSend_P(PSTR("\"eb('i75%d').value='%d';\">"),
i, changeUIntScale(Dali->web_dimmer[i], 0, 254, 0, 100));
}
WSContentSend_P(PSTR("eb('i75%d').value='%d';"),
i, changeUIntScale(Dali->dimmer[i], 0, 254, 0, 100));
}
}
WSContentSend_P(PSTR("\">{t}")); // Restart {t} = <table style='width:100%'>
WSContentSeparator(3); // Don't print separator on next WSContentSeparator(1)
}
}
#endif // USE_WEBSERVER
}
}