diff --git a/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino b/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino index a452417ef..888f7f8fd 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino @@ -267,12 +267,15 @@ const char HTTP_HEAD_STYLE3[] PROGMEM = "

%s

"; // Device name const char HTTP_MSG_SLIDER_GRADIENT[] PROGMEM = + "" "
" "" - "
"; + "" + ""; -const char HTTP_MSG_SLIDER_UPDATE[] PROGMEM = - "
"; @@ -416,7 +419,7 @@ const char HTTP_END[] PROGMEM = "" ""; -const char HTTP_DEVICE_CONTROL[] PROGMEM = ""; // ?o is related to WebGetArg(PSTR("o"), tmp, sizeof(tmp)) +const char HTTP_DEVICE_CONTROL[] PROGMEM = ""; // ?o is related to WebGetArg(PSTR("o"), tmp, sizeof(tmp)) const char HTTP_DEVICE_STATE[] PROGMEM = "%s"; enum ButtonTitle { @@ -462,6 +465,7 @@ ESP8266WebServer *Webserver; struct WEB { String chunk_buffer = ""; // Could be max 2 * CHUNKED_BUFFER_SIZE uint32_t upload_size = 0; + uint32_t slider_update_time = 0; int slider[LST_MAX]; uint16_t upload_error = 0; uint8_t state = HTTP_OFF; @@ -1150,31 +1154,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("")); WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, // Cold Warm - PSTR("a"), // a - Unique HTML id + 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("")); } #endif // USE_LIGHT -void HandleRoot(void) -{ +const char HTTP_MSG_SLIDER_SHUTTERT[] PROGMEM = + "" + "
%s" + "" + "
" + ""; + +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 +1209,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 +1230,131 @@ void HandleRoot(void) WSContentSend_P(HTTP_SCRIPT_ROOT, Settings->web_refresh); #endif WSContentSend_P(HTTP_SCRIPT_ROOT_PART2); - WSContentSendStyle(); WSContentSend_P(PSTR("
")); + +#ifndef FIRMWARE_MINIMAL + 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); // "" + WSContentSend_P(PSTR("")); + +#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("")); } + } +#ifdef USE_SONOFF_IFAN + } +#endif // USE_SONOFF_IFAN + + WSContentSend_P(PSTR("
")); + } + +#ifdef USE_SHUTTER + if (shutter_button) { // Any button bit set + WSContentSend_P(HTTP_TABLE100); // "" + + 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("")); + 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) ? "▼" : "▲") : ((ShutterWebButton>0) ? "▲" : "▼"))), + ""); + + 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("")); + shutter_button_idx += 2; + + } + WSContentSend_P(PSTR("
")); + } +#endif // USE_SHUTTER + #ifdef USE_LIGHT if (TasmotaGlobal.light_type) { + WSContentSend_P(HTTP_TABLE100); // "" + 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 +1366,16 @@ void HandleRoot(void) LightGetHSB(&hue, &sat, nullptr); Web.slider[1] = hue; + WSContentSend_P(PSTR("")); WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, // Hue - PSTR("b"), // b - Unique HTML id + 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("")); uint8_t dcolor = changeUIntScale(Settings->light_dimmer, 0, 100, 0, 255); char scolor[8]; @@ -1252,97 +1385,137 @@ 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("")); WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT, // Saturation - 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%%))'; + 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("")); } + 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("")); + 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 - PSTR("c"), // c - Unique HTML id + 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("")); if (split_white) { // SetOption37 128 if (LST_RGBCW == light_subtype) { WebSliderColdWarm(); } + + uint32_t width = 100; + WSContentSend_P(PSTR("")); + + 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 - PSTR("f"), // f - Unique HTML id - PSTR("#000"), PSTR("#fff"), // 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("")); } } 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("")); + 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("")); } } // Settings->flag3.pwm_multi_channels + WSContentSend_P(PSTR("
")); } #endif // USE_LIGHT - WSContentSend_P(HTTP_TABLE100); - WSContentSend_P(PSTR("")); -#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, - ""); - } - } 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) ? "▼" : "▲") : ((ShutterWebButton>0) ? "▲" : "▼"))), - ""); - } 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("")); } - } -#ifdef USE_SONOFF_IFAN - } -#endif // USE_SONOFF_IFAN - WSContentSend_P(PSTR("")); + } -#ifndef FIRMWARE_MINIMAL + // Init buttons + WSContentSend_P(PSTR("")); + XdrvXsnsCall(FUNC_WEB_ADD_MAIN_BUTTON); #endif // Not FIRMWARE_MINIMAL @@ -1383,6 +1556,8 @@ bool HandleRootStatusRefresh(void) return false; } +#ifndef FIRMWARE_MINIMAL + #ifdef USE_SCRIPT_WEB_DISPLAY Script_Check_HTML_Setvars(); #endif @@ -1405,19 +1580,6 @@ bool HandleRootStatusRefresh(void) } } else { #endif // USE_SONOFF_IFAN -#ifdef USE_TUYA_MCU - if (IsModuleTuya()) { - if (device <= TasmotaGlobal.devices_present) { - ExecuteCommandPower(device, POWER_TOGGLE, SRC_IGNORE); - } else { - if (AsModuleTuyaMS() && device == TasmotaGlobal.devices_present + 1) { - uint8_t dpId = TuyaGetDpId(TUYA_MCU_FUNC_MODESET); - snprintf_P(svalue, sizeof(svalue), PSTR("Tuyasend4 %d,%d"), dpId, !TuyaModeSet()); - ExecuteCommand(svalue, SRC_WEBGUI); - } - } - } else { -#endif // USE_TUYA_MCU #ifdef USE_SHUTTER int32_t ShutterWebButton; if (ShutterWebButton = IsShutterWebButton(device)) { @@ -1432,9 +1594,6 @@ bool HandleRootStatusRefresh(void) #ifdef USE_SONOFF_IFAN } #endif // USE_SONOFF_IFAN -#ifdef USE_TUYA_MCU - } -#endif // USE_TUYA_MCU } #ifdef USE_LIGHT WebGetArg(PSTR("d0"), tmp, sizeof(tmp)); // 0 - 100 Dimmer value @@ -1507,44 +1666,99 @@ 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); // " 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); // "flag6.disable_slider_updates) { // SetOption161 0 - (Light) Disable slider updates (1) - uint16_t hue; - uint8_t sat; - int current_value = -1; - for (uint32_t i = 0; i < LST_MAX; i++) { - if (Web.slider[i] != -1) { - if (!Settings->flag3.pwm_multi_channels) { // SetOption68 0 - Enable multi-channels PWM instead of Color PWM - if (0 == i) { - current_value = LightGetColorTemp(); - } - else if (1 == i) { - LightGetHSB(&hue, &sat, nullptr); - current_value = hue; - } - else if (2 == i) { - current_value = changeUIntScale(sat, 0, 255, 0, 100); - } - else if (3 == i) { - current_value = Settings->light_dimmer; - } - else if (4 == i) { - current_value = LightGetDimmer(2); - } - } else { - current_value = changeUIntScale(Settings->light_color[i], 0, 255, 0, 100); + uint16_t hue; + uint8_t sat; + int current_value = -1; + uint32_t slider_update_time = millis(); + for (uint32_t i = 0; i < LST_MAX; i++) { + if (Web.slider[i] != -1) { + if (!Settings->flag3.pwm_multi_channels) { // SetOption68 0 - Enable multi-channels PWM instead of Color PWM + if (0 == i) { + current_value = LightGetColorTemp(); } - if (current_value != Web.slider[i]) { + else if (1 == i) { + LightGetHSB(&hue, &sat, nullptr); + current_value = hue; + } + else if (2 == i) { + current_value = changeUIntScale(sat, 0, 255, 0, 100); + } + else if (3 == i) { + current_value = Settings->light_dimmer; + } + else if (4 == i) { + current_value = LightGetDimmer(2); + } + } else { + current_value = changeUIntScale(Settings->light_color[i], 0, 255, 0, 100); + } + if (current_value != Web.slider[i]) { + if (0 == Web.slider_update_time) { + Web.slider_update_time = slider_update_time + Settings->web_refresh; // Allow other users to sync screen + } + else if (slider_update_time > Web.slider_update_time) { + Web.slider_update_time = 1; // Allow multiple updates 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); // ""), i +1, current_value); } + if (!msg_exec_javascript) { + WSContentSend_P(HTTP_MSG_EXEC_JAVASCRIPT); // "")); + } + WSContentSend_P(PSTR("{t}")); // WSContentSeparator(3); // Reset seperator to ignore previous outputs if (Settings->web_time_end) { @@ -1554,33 +1768,11 @@ bool HandleRootStatusRefresh(void) XsnsXdrvCall(FUNC_WEB_SENSOR); WSContentSend_P(PSTR("
")); - if (TasmotaGlobal.devices_present) { - WSContentSend_P(PSTR("{t}")); -#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("")); } - } -#ifdef USE_SONOFF_IFAN - } -#endif // USE_SONOFF_IFAN - - WSContentSend_P(PSTR("")); - } WSContentSend_P(PSTR("\n\n")); // Prep for SSE WSContentEnd(); +#endif // not FIRMWARE_MINIMAL + return true; } @@ -1754,12 +1946,12 @@ void HandleTemplateConfiguration(void) { WSContentSendStyle(); WSContentSend_P(HTTP_FORM_TEMPLATE); - WSContentSend_P(HTTP_TABLE100); + WSContentSend_P(HTTP_TABLE100); // "" WSContentSend_P(PSTR("" "" "
" D_TEMPLATE_NAME "
" D_BASE_TYPE "
" "
")); - WSContentSend_P(HTTP_TABLE100); + WSContentSend_P(HTTP_TABLE100); // "" 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 diff --git a/tasmota/tasmota_xdrv_driver/xdrv_16_tuyamcu_v1.ino b/tasmota/tasmota_xdrv_driver/xdrv_16_tuyamcu_v1.ino index 9e8e4d462..869bf6141 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_16_tuyamcu_v1.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_16_tuyamcu_v1.ino @@ -1620,18 +1620,32 @@ void TuyaSensorsShow(bool json) #ifdef USE_WEBSERVER +#define WEB_HANDLE_TUYA "d16" + void TuyaAddButton(void) { if (AsModuleTuyaMS()) { WSContentSend_P(HTTP_TABLE100); - WSContentSend_P(PSTR("
")); char stemp[33]; - snprintf_P(stemp, sizeof(stemp), PSTR("" D_JSON_IRHVAC_MODE "")); - WSContentSend_P(HTTP_DEVICE_CONTROL, 26, TasmotaGlobal.devices_present + 1, - (strlen(GetWebButton(TasmotaGlobal.devices_present))) ? HtmlEscape(GetWebButton(TasmotaGlobal.devices_present)).c_str() : stemp, ""); + snprintf_P(stemp, sizeof(stemp), PSTR(D_JSON_IRHVAC_MODE)); + WSContentSend_P(PSTR(""), // &d16 is related to WebGetArg("d16", tmp, sizeof(tmp)); + (strlen(GetWebButton(TasmotaGlobal.devices_present))) ? HtmlEscape(GetWebButton(TasmotaGlobal.devices_present)).c_str() : stemp); WSContentSend_P(PSTR("
")); } } +void TuyaWebGetArg(void) { + if (AsModuleTuyaMS()) { + char tmp[8]; // WebGetArg numbers only + WebGetArg(PSTR(WEB_HANDLE_TUYA), tmp, sizeof(tmp)); + if (strlen(tmp)) { + uint8_t dpId = TuyaGetDpId(TUYA_MCU_FUNC_MODESET); + char svalue[32]; + snprintf_P(svalue, sizeof(svalue), PSTR("Tuyasend4 %d,%d"), dpId, !TuyaModeSet()); + ExecuteWebCommand(svalue); + } + } +} + #endif // USE_WEBSERVER /*********************************************************************************************\ @@ -1724,6 +1738,9 @@ bool Xdrv16(uint32_t function) { case FUNC_WEB_ADD_MAIN_BUTTON: TuyaAddButton(); break; + case FUNC_WEB_GET_ARG: + TuyaWebGetArg(); + break; case FUNC_WEB_SENSOR: TuyaSensorsShow(0); break; diff --git a/tasmota/tasmota_xdrv_driver/xdrv_16_tuyamcu_v2.ino b/tasmota/tasmota_xdrv_driver/xdrv_16_tuyamcu_v2.ino index 9d4364552..31b47e286 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_16_tuyamcu_v2.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_16_tuyamcu_v2.ino @@ -2413,18 +2413,32 @@ void TuyaSensorsShow(bool json) #ifdef USE_WEBSERVER +#define WEB_HANDLE_TUYA "d16" + void TuyaAddButton(void) { if (AsModuleTuyaMS()) { WSContentSend_P(HTTP_TABLE100); - WSContentSend_P(PSTR("
")); char stemp[33]; - snprintf_P(stemp, sizeof(stemp), PSTR("" D_JSON_IRHVAC_MODE "")); - WSContentSend_P(HTTP_DEVICE_CONTROL, 26, TasmotaGlobal.devices_present + 1, - (strlen(GetWebButton(TasmotaGlobal.devices_present))) ? HtmlEscape(GetWebButton(TasmotaGlobal.devices_present)).c_str() : stemp, ""); + snprintf_P(stemp, sizeof(stemp), PSTR(D_JSON_IRHVAC_MODE)); + WSContentSend_P(PSTR(""), // &d16 is related to WebGetArg("d16", tmp, sizeof(tmp)); + (strlen(GetWebButton(TasmotaGlobal.devices_present))) ? HtmlEscape(GetWebButton(TasmotaGlobal.devices_present)).c_str() : stemp); WSContentSend_P(PSTR("")); } } +void TuyaWebGetArg(void) { + if (AsModuleTuyaMS()) { + char tmp[8]; // WebGetArg numbers only + WebGetArg(PSTR(WEB_HANDLE_TUYA), tmp, sizeof(tmp)); + if (strlen(tmp)) { + uint8_t dpId = TuyaGetDpId(TUYA_MCU_FUNC_MODESET); + char svalue[32]; + snprintf_P(svalue, sizeof(svalue), PSTR("Tuyasend4 %d,%d"), dpId, !TuyaModeSet()); + ExecuteWebCommand(svalue); + } + } +} + #endif // USE_WEBSERVER /*********************************************************************************************\ @@ -2559,6 +2573,9 @@ bool Xdrv16(uint32_t function) { case FUNC_WEB_ADD_MAIN_BUTTON: TuyaAddButton(); break; + case FUNC_WEB_GET_ARG: + TuyaWebGetArg(); + break; case FUNC_WEB_SENSOR: TuyaSensorsShow(0); break; diff --git a/tasmota/tasmota_xdrv_driver/xdrv_27_esp32_shutter.ino b/tasmota/tasmota_xdrv_driver/xdrv_27_esp32_shutter.ino index 3f0ec5201..d166f4460 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_27_esp32_shutter.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_27_esp32_shutter.ino @@ -1164,14 +1164,6 @@ void ShutterSettingsSave(void) { } } -void ShutterShow() -{ - for (uint32_t i = 0; i < TasmotaGlobal.shutters_present; i++) { - WSContentSend_P(HTTP_MSG_SLIDER_SHUTTER, (ShutterGetOptions(i) & 1) ? D_OPEN : D_CLOSE,(ShutterGetOptions(i) & 1) ? D_CLOSE : D_OPEN, (ShutterGetOptions(i) & 1) ? (100 - ShutterRealToPercentPosition(-9999, i)) : ShutterRealToPercentPosition(-9999, i), i+1); - WSContentSeparator(3); // Don't print separator on next WSContentSeparator(1) - } -} - void ShutterStartInit(uint32_t i, int32_t direction, int32_t target_pos) { //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: dir %d, delta1 %d, delta2 %d"),direction, (Shutter[i].open_max - Shutter[i].real_position) / Shutter[i].close_velocity, Shutter[i].real_position / Shutter[i].close_velocity); @@ -2394,11 +2386,6 @@ bool Xdrv27(uint32_t function) result = false; } break; -#ifdef USE_WEBSERVER - case FUNC_WEB_SENSOR: - ShutterShow(); - break; -#endif // USE_WEBSERVER case FUNC_ACTIVE: result = true; break; diff --git a/tasmota/tasmota_xdrv_driver/xdrv_27_shutter.ino b/tasmota/tasmota_xdrv_driver/xdrv_27_shutter.ino index a6d1d0da0..642d78154 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_27_shutter.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_27_shutter.ino @@ -1142,13 +1142,6 @@ void ShutterToggle(bool dir) } } -void ShutterShow(){ - for (uint32_t i = 0; i < TasmotaGlobal.shutters_present; i++) { - WSContentSend_P(HTTP_MSG_SLIDER_SHUTTER, (Settings->shutter_options[i] & 1) ? D_OPEN : D_CLOSE,(Settings->shutter_options[i] & 1) ? D_CLOSE : D_OPEN, (Settings->shutter_options[i] & 1) ? (100 - ShutterRealToPercentPosition(-9999, i)) : ShutterRealToPercentPosition(-9999, i), i+1); - WSContentSeparator(3); // Don't print separator on next WSContentSeparator(1) - } -} - /*********************************************************************************************\ * Commands \*********************************************************************************************/ @@ -1976,11 +1969,6 @@ bool Xdrv27(uint32_t function) result = true; } break; -#ifdef USE_WEBSERVER - case FUNC_WEB_SENSOR: - ShutterShow(); - break; -#endif // USE_WEBSERVER case FUNC_ACTIVE: result = true; break; diff --git a/tasmota/tasmota_xdrv_driver/xdrv_75_dali.ino b/tasmota/tasmota_xdrv_driver/xdrv_75_dali.ino index ce0e6740d..d56f22e4e 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_75_dali.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_75_dali.ino @@ -292,6 +292,7 @@ struct DALI { uint32_t bit_cycles; uint32_t last_activity; uint32_t received_dali_data; // Data received from DALI bus + uint32_t slider_update_time; uint8_t pin_rx; uint8_t pin_tx; uint8_t max_short_address; @@ -304,7 +305,6 @@ struct DALI { 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 - WebColor((Dali->web_power[i])?COL_BUTTON:COL_BACKGROUND), + WebColor((Dali->power[i]) ? COL_BUTTON : COL_FORM), i, // k75= (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,29 @@ 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); // ""), - i, WebColor((Dali->web_power[i])?COL_BUTTON:COL_BACKGROUND)); - WSContentSeparator(3); // Don't print separator on next WSContentSeparator(1) - } + WSContentSend_P(PSTR("")); // Terminate current {t} + WSContentSend_P(HTTP_MSG_EXEC_JAVASCRIPT); // "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_dimmer[i] = Dali->dimmer[i]; - WSContentSend_P(HTTP_MSG_SLIDER_UPDATE); // ""), - i, changeUIntScale(Dali->web_dimmer[i], 0, 254, 0, 100)); - WSContentSeparator(3); // Don't print separator on next WSContentSeparator(1) + if (0 == Dali->slider_update_time) { + Dali->slider_update_time = slider_update_time + Settings->web_refresh; // Allow other users to sync screen + } + else if (slider_update_time > Dali->slider_update_time) { + Dali->slider_update_time = 1; // Allow multiple updates + Dali->web_dimmer[i] = Dali->dimmer[i]; + } + WSContentSend_P(PSTR("eb('i75%d').value='%d';"), + i, changeUIntScale(Dali->dimmer[i], 0, 254, 0, 100)); } } + if (1 == Dali->slider_update_time) { + Dali->slider_update_time = 0; + } + WSContentSend_P(PSTR("\">{t}")); // Restart {t} = + WSContentSeparator(3); // Don't print separator on next WSContentSeparator(1) #endif // USE_WEBSERVER } }