diff --git a/esp3d/webinterface.cpp b/esp3d/webinterface.cpp index 8f53b4e3..b0f2a4ac 100644 --- a/esp3d/webinterface.cpp +++ b/esp3d/webinterface.cpp @@ -211,6 +211,69 @@ const char KEY_LOGIN_ID [] PROGMEM = "$LOGIN_ID$"; const char KEY_IS_DEFAULT_MODE [] PROGMEM = "$IS_DEFAULT_MODE$"; +bool WEBINTERFACE_CLASS::generateJSON(STORESTRINGS_CLASS & KeysList , STORESTRINGS_CLASS & ValuesList ) +{ + LOG("process JSON\r\n") + if(KeysList.size() != ValuesList.size()) { //Sanity check + Serial.print("M117 Error"); + LOG("Error\r\n") + return false; + } + bool header_sent=false; + String buffer2send="{"; + for (int pos = 0; pos < KeysList.size(); pos++){ + //add current entry to buffer + if (pos > 0)buffer2send+=","; + String keystring = KeysList.get(pos); + keystring.replace(String("$"),String("")); + //this allow to add extra info in array + buffer2send+="\""; + buffer2send+=keystring; + buffer2send+="\":"; + if (ValuesList.get(pos)[0]!='\"')buffer2send+="\""; + buffer2send+=ValuesList.get(pos); + if (ValuesList.get(pos)[0]!='\"')buffer2send+="\""; + + //if buffer limit is reached + if (buffer2send.length()>1200) { + //if header is not send yet + if (!header_sent) { + //send header with calculated size + header_sent=true; + web_interface->WebServer.setContentLength(CONTENT_LENGTH_UNKNOWN); + web_interface->WebServer.sendHeader("Content-Type","application/json",true); + web_interface->WebServer.sendHeader("Cache-Control","no-cache"); + web_interface->WebServer.send(200); + } + //send data + web_interface->WebServer.sendContent(buffer2send); + //reset buffer + buffer2send=""; + } + //add a delay for safety for WDT + delay(0); + } + buffer2send+="}"; + //check if something is still in buffer and need to be send + if (buffer2send!="") { + //if header is not send yet + if (!header_sent) { + //send header + web_interface->WebServer.setContentLength(CONTENT_LENGTH_UNKNOWN); + web_interface->WebServer.sendHeader("Content-Type","application/json",true); + web_interface->WebServer.sendHeader("Cache-Control","no-cache"); + web_interface->WebServer.send(200); + } + //send data + web_interface->WebServer.sendContent(buffer2send); + } + //close line + web_interface->WebServer.sendContent(""); + LOG("Process template done\r\n") + return true; +} + + bool WEBINTERFACE_CLASS::processTemplate(const char * filename, STORESTRINGS_CLASS & KeysList , STORESTRINGS_CLASS & ValuesList ) { LOG("process template\r\n") @@ -412,13 +475,13 @@ void WEBINTERFACE_CLASS::GetFreeMem(STORESTRINGS_CLASS & KeysList, STORESTRINGS_ // ----------------------------------------------------------------------------- // Helper for Login ID // ----------------------------------------------------------------------------- -void WEBINTERFACE_CLASS::GeLogin(STORESTRINGS_CLASS & KeysList, STORESTRINGS_CLASS & ValuesList,level_authenticate_type auth_level) +void WEBINTERFACE_CLASS::GetLogin(STORESTRINGS_CLASS & KeysList, STORESTRINGS_CLASS & ValuesList,level_authenticate_type auth_level,bool ishtmloutput) { - KeysList.add(FPSTR(KEY_DISCONNECT_VISIBILITY)); + if (ishtmloutput)KeysList.add(FPSTR(KEY_DISCONNECT_VISIBILITY)); #ifdef AUTHENTICATION_FEATURE if (auth_level != LEVEL_GUEST) { - ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + if (ishtmloutput)ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); KeysList.add(FPSTR(KEY_LOGIN_ID)); if (auth_level == LEVEL_ADMIN) { ValuesList.add(FPSTR(DEFAULT_ADMIN_LOGIN)); @@ -428,7 +491,7 @@ void WEBINTERFACE_CLASS::GeLogin(STORESTRINGS_CLASS & KeysList, STORESTRINGS_CLA } else #endif { - ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + if (ishtmloutput)ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); KeysList.add(FPSTR(KEY_LOGIN_ID)); ValuesList.add(""); } @@ -478,20 +541,20 @@ void WEBINTERFACE_CLASS::GetMode(STORESTRINGS_CLASS & KeysList, STORESTRINGS_CLA // ----------------------------------------------------------------------------- // Helper for Web ports // ----------------------------------------------------------------------------- -void WEBINTERFACE_CLASS::GetPorts(STORESTRINGS_CLASS & KeysList, STORESTRINGS_CLASS & ValuesList) +void WEBINTERFACE_CLASS::GetPorts(STORESTRINGS_CLASS & KeysList, STORESTRINGS_CLASS & ValuesList, bool ishtmloutput) { //Web port KeysList.add(FPSTR(KEY_WEB_PORT)); ValuesList.add(CONFIG::intTostr(wifi_config.iweb_port)); //Data port KeysList.add(FPSTR(KEY_DATA_PORT)); - KeysList.add(FPSTR(KEY_DATA_PORT_VISIBILITY)); + if(ishtmloutput)KeysList.add(FPSTR(KEY_DATA_PORT_VISIBILITY)); #ifdef TCP_IP_DATA_FEATURE ValuesList.add(CONFIG::intTostr(wifi_config.idata_port)); - ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + if(ishtmloutput)ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); #else ValuesList.add(FPSTR(VALUE_NONE)); - ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + if(ishtmloutput)ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); #endif } // ----------------------------------------------------------------------------- @@ -537,56 +600,62 @@ void WEBINTERFACE_CLASS::GetDHCPStatus(STORESTRINGS_CLASS & KeysList, STORESTRIN // ----------------------------------------------------------------------------- // Helper for Error Msg processing // ----------------------------------------------------------------------------- -void WEBINTERFACE_CLASS::ProcessAlertError(STORESTRINGS_CLASS & KeysList, STORESTRINGS_CLASS & ValuesList, String & smsg) +void WEBINTERFACE_CLASS::ProcessAlertError(STORESTRINGS_CLASS & KeysList, STORESTRINGS_CLASS & ValuesList, String & smsg, bool ishtmloutput) { KeysList.add(FPSTR(KEY_ERROR_MSG)); ValuesList.add(smsg); - KeysList.add(FPSTR(KEY_SUCCESS_MSG)); - ValuesList.add(""); - KeysList.add(FPSTR(KEY_ERROR_MSG_VISIBILITY )); - ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); - KeysList.add(FPSTR(KEY_SUCCESS_MSG_VISIBILITY)); - ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); - KeysList.add(FPSTR(KEY_SUBMIT_BUTTON_VISIBILITY)); - ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); - KeysList.add(FPSTR(KEY_SERVICE_PAGE)); - ValuesList.add(""); + if (ishtmloutput){ + KeysList.add(FPSTR(KEY_SUCCESS_MSG)); + ValuesList.add(""); + KeysList.add(FPSTR(KEY_ERROR_MSG_VISIBILITY )); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + KeysList.add(FPSTR(KEY_SUCCESS_MSG_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + KeysList.add(FPSTR(KEY_SUBMIT_BUTTON_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + KeysList.add(FPSTR(KEY_SERVICE_PAGE)); + ValuesList.add(""); + } } // ----------------------------------------------------------------------------- // Helper for Success Msg processing // ----------------------------------------------------------------------------- -void WEBINTERFACE_CLASS::ProcessAlertSuccess(STORESTRINGS_CLASS & KeysList, STORESTRINGS_CLASS & ValuesList, String & smsg) +void WEBINTERFACE_CLASS::ProcessAlertSuccess(STORESTRINGS_CLASS & KeysList, STORESTRINGS_CLASS & ValuesList, String & smsg, bool ishtmloutput) { KeysList.add(FPSTR(KEY_ERROR_MSG)); ValuesList.add(""); - KeysList.add(FPSTR(KEY_SUCCESS_MSG)); - ValuesList.add(smsg); - KeysList.add(FPSTR(KEY_ERROR_MSG_VISIBILITY )); - ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); - KeysList.add(FPSTR(KEY_SUCCESS_MSG_VISIBILITY)); - ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); - KeysList.add(FPSTR(KEY_SUBMIT_BUTTON_VISIBILITY)); - ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + if (ishtmloutput){ + KeysList.add(FPSTR(KEY_SUCCESS_MSG)); + ValuesList.add(smsg); + KeysList.add(FPSTR(KEY_ERROR_MSG_VISIBILITY )); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + KeysList.add(FPSTR(KEY_SUCCESS_MSG_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + KeysList.add(FPSTR(KEY_SUBMIT_BUTTON_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + } } // ----------------------------------------------------------------------------- // Helper for No Msg processing // ----------------------------------------------------------------------------- -void WEBINTERFACE_CLASS::ProcessNoAlert(STORESTRINGS_CLASS & KeysList, STORESTRINGS_CLASS & ValuesList) +void WEBINTERFACE_CLASS::ProcessNoAlert(STORESTRINGS_CLASS & KeysList, STORESTRINGS_CLASS & ValuesList, bool ishtmloutput) { KeysList.add(FPSTR(KEY_ERROR_MSG)); ValuesList.add(""); - KeysList.add(FPSTR(KEY_SUCCESS_MSG)); - ValuesList.add(""); - KeysList.add(FPSTR(KEY_ERROR_MSG_VISIBILITY )); - ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); - KeysList.add(FPSTR(KEY_SUCCESS_MSG_VISIBILITY)); - ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); - KeysList.add(FPSTR(KEY_SUBMIT_BUTTON_VISIBILITY)); - ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); - KeysList.add(FPSTR(KEY_SERVICE_PAGE)); - ValuesList.add(""); + if (ishtmloutput){ + KeysList.add(FPSTR(KEY_SUCCESS_MSG)); + ValuesList.add(""); + KeysList.add(FPSTR(KEY_ERROR_MSG_VISIBILITY )); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + KeysList.add(FPSTR(KEY_SUCCESS_MSG_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + KeysList.add(FPSTR(KEY_SUBMIT_BUTTON_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + KeysList.add(FPSTR(KEY_SERVICE_PAGE)); + ValuesList.add(""); + } } //root insterface @@ -622,9 +691,16 @@ void handle_web_interface_home() struct softap_config apconfig; struct ip_info info; uint8_t mac [WL_MAC_ADDR_LENGTH]; + bool outputjson = false; + if (web_interface->WebServer.hasArg("output")){ + if (web_interface->WebServer.arg("output") == "JSON") + { + outputjson = true; + } + } LOG("request /HOME\r\n") //login - web_interface->GeLogin(KeysList, ValuesList,web_interface->is_authenticated()); + web_interface->GetLogin(KeysList, ValuesList,web_interface->is_authenticated(), !outputjson); //IP+Web web_interface->GetIpWeb(KeysList, ValuesList); @@ -635,13 +711,17 @@ void handle_web_interface_home() ValuesList.add(FPSTR(VALUE_STA)); KeysList.add(FPSTR(KEY_HOSTNAME)); ValuesList.add(wifi_config.get_hostname()); - KeysList.add(FPSTR(KEY_HOSTNAME_VISIBLE)); - ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + if (!outputjson){ + KeysList.add(FPSTR(KEY_HOSTNAME_VISIBLE)); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + } } else { KeysList.add(FPSTR(KEY_HOSTNAME)); ValuesList.add(FPSTR(KEY_NOT_APPLICABLE_4_AP)); - KeysList.add(FPSTR(KEY_HOSTNAME_VISIBLE)); - ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + if (!outputjson){ + KeysList.add(FPSTR(KEY_HOSTNAME_VISIBLE)); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + } if (WiFi.getMode()==WIFI_AP ) { KeysList.add(FPSTR(KEY_MODE)); ValuesList.add(FPSTR(VALUE_AP)); @@ -650,13 +730,13 @@ void handle_web_interface_home() ValuesList.add(FPSTR(VALUE_AP_STA)); } } - - //page title and filenames - web_interface->SetPageProp(KeysList,ValuesList,FPSTR(VALUE_HOME),F("home")); - //menu item - KeysList.add(FPSTR(KEY_MENU_HOME)); - ValuesList.add(FPSTR(VALUE_ACTIVE)); - + if (!outputjson){ + //page title and filenames + web_interface->SetPageProp(KeysList,ValuesList,FPSTR(VALUE_HOME),F("home")); + //menu item + KeysList.add(FPSTR(KEY_MENU_HOME)); + ValuesList.add(FPSTR(VALUE_ACTIVE)); + } //Chip ID KeysList.add(FPSTR(KEY_CHIP_ID)); ValuesList.add(CONFIG::intTostr(system_get_chip_id())); @@ -674,26 +754,34 @@ void handle_web_interface_home() stmp+=wifi_config.get_hostname(); stmp+=".local"; ValuesList.add(stmp); - KeysList.add(FPSTR(KEY_MDNS_VISIBLE)); - ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + if (!outputjson){ + KeysList.add(FPSTR(KEY_MDNS_VISIBLE)); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + } #else - KeysList.add(FPSTR(KEY_MDNS_NAME)); - ValuesList.add(FPSTR(VALUE_DISABLED)); - KeysList.add(FPSTR(KEY_MDNS_VISIBLE)); - ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + if (!outputjson){ + KeysList.add(FPSTR(KEY_MDNS_NAME)); + ValuesList.add(FPSTR(VALUE_DISABLED)); + KeysList.add(FPSTR(KEY_MDNS_VISIBLE)); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + } #endif //SSDP Feature #ifdef SSDP_FEATURE KeysList.add(FPSTR(KEY_SSDP_STATUS)); ValuesList.add(FPSTR(VALUE_ENABLED)); - KeysList.add(FPSTR(KEY_SSDP_VISIBLE)); - ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + if (!outputjson){ + KeysList.add(FPSTR(KEY_SSDP_VISIBLE)); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + } #else KeysList.add(FPSTR(KEY_SSDP_STATUS)); ValuesList.add(FPSTR(VALUE_DISABLED)); - KeysList.add(FPSTR(KEY_SSDP_VISIBLE)); - ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + if (!outputjson){ + KeysList.add(FPSTR(KEY_SSDP_VISIBLE)); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + } #endif //Captive portal Feature @@ -701,19 +789,25 @@ void handle_web_interface_home() if (WiFi.getMode()==WIFI_AP) { KeysList.add(FPSTR(KEY_CAPTIVE_PORTAL_STATUS)); ValuesList.add(FPSTR(VALUE_ENABLED)); - KeysList.add(FPSTR(KEY_CAPTIVE_PORTAL_VISIBLE)); - ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + if (!outputjson){ + KeysList.add(FPSTR(KEY_CAPTIVE_PORTAL_VISIBLE)); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + } } else { KeysList.add(FPSTR(KEY_CAPTIVE_PORTAL_STATUS)); ValuesList.add(FPSTR(VALUE_DISABLED)); - KeysList.add(FPSTR(KEY_CAPTIVE_PORTAL_VISIBLE)); - ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + if (!outputjson){ + KeysList.add(FPSTR(KEY_CAPTIVE_PORTAL_VISIBLE)); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + } } #else KeysList.add(FPSTR(KEY_CAPTIVE_PORTAL_STATUS)); ValuesList.add(FPSTR(VALUE_DISABLED)); - KeysList.add(FPSTR(KEY_CAPTIVE_PORTAL_VISIBLE)); - ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + if (!outputjson){ + KeysList.add(FPSTR(KEY_CAPTIVE_PORTAL_VISIBLE)); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + } #endif //network @@ -741,7 +835,7 @@ void handle_web_interface_home() KeysList.add(FPSTR(KEY_BAUD_RATE)); ValuesList.add(CONFIG::intTostr(wifi_config.baud_rate)); // Web and Data ports - web_interface->GetPorts(KeysList, ValuesList); + web_interface->GetPorts(KeysList, ValuesList, !outputjson); //AP part if (WiFi.getMode()==WIFI_AP || WiFi.getMode()==WIFI_AP_STA) { @@ -751,8 +845,10 @@ void handle_web_interface_home() KeysList.add(FPSTR(KEY_AP_STATUS_ENABLED)); ValuesList.add(FPSTR(VALUE_ENABLED)); //set visible - KeysList.add(FPSTR(KEY_AP_VISIBILITY)); - ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + if (!outputjson){ + KeysList.add(FPSTR(KEY_AP_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + } //list of connected clients station = wifi_softap_get_station_info(); while(station) { @@ -782,9 +878,11 @@ void handle_web_interface_home() //AP is disabled KeysList.add(FPSTR(KEY_AP_STATUS_ENABLED)); ValuesList.add(FPSTR(VALUE_DISABLED)); - //set hide - KeysList.add(FPSTR(KEY_AP_VISIBILITY)); - ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + if (!outputjson){ + //set hide + KeysList.add(FPSTR(KEY_AP_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + } //Connected clients KeysList.add(FPSTR(KEY_CONNECTED_STATIONS_NB_ITEMS)); ValuesList.add("0"); @@ -870,16 +968,20 @@ void handle_web_interface_home() //STA is enabled KeysList.add(FPSTR(KEY_STA_STATUS_ENABLED)); ValuesList.add(FPSTR(VALUE_ENABLED)); - //set visible - KeysList.add(FPSTR(KEY_STA_VISIBILITY)); - ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + if (!outputjson){ + //set visible + KeysList.add(FPSTR(KEY_STA_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_VISIBLE)); + } } else { //STA is disabled KeysList.add(FPSTR(KEY_STA_STATUS_ENABLED)); ValuesList.add(FPSTR(VALUE_DISABLED)); - //set hide - KeysList.add(FPSTR(KEY_STA_VISIBILITY)); - ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + if (!outputjson){ + //set hide + KeysList.add(FPSTR(KEY_STA_VISIBILITY)); + ValuesList.add(FPSTR(VALUE_ITEM_HIDDEN)); + } } //STA mac address KeysList.add(FPSTR(KEY_STA_MAC)); @@ -924,13 +1026,18 @@ void handle_web_interface_home() //Sub Net Mask KeysList.add(FPSTR(KEY_STA_SUBNET)); ValuesList.add(WiFi.subnetMask().toString().c_str()); - //Service page / no need refresh on this page - KeysList.add(FPSTR(KEY_SERVICE_PAGE)); - ValuesList.add(""); + if (!outputjson){ + //Service page / no need refresh on this page + KeysList.add(FPSTR(KEY_SERVICE_PAGE)); + ValuesList.add(""); + } //Firmware & Free Mem, at the end to reflect situation web_interface->GetFreeMem(KeysList, ValuesList); - //process the template file and provide list of variables - web_interface->processTemplate("/home.tpl", KeysList , ValuesList); + //check if need template or do a JSON + if (outputjson){ + web_interface->generateJSON(KeysList , ValuesList); + }//process the template file and provide list of variables + else web_interface->processTemplate("/home.tpl", KeysList , ValuesList); //need to clean to speed up memory recovery KeysList.clear(); ValuesList.clear(); @@ -956,24 +1063,31 @@ void handle_web_interface_configSys() const __FlashStringHelper *smodemdisplaylist[]= {FPSTR(VALUE_NONE),FPSTR(VALUE_LIGHT),FPSTR(VALUE_MODEM),FPSTR(VALUE_MODEM)}; STORESTRINGS_CLASS KeysList ; STORESTRINGS_CLASS ValuesList ; - + bool outputjson = false; + if (web_interface->WebServer.hasArg("output")){ + if (web_interface->WebServer.arg("output") == "JSON") + { + outputjson = true; + } + } level_authenticate_type auth_level= web_interface->is_authenticated(); if (auth_level != LEVEL_ADMIN) { web_interface->WebServer.sendContent_P(NOT_AUTH_CS); return; } //login - web_interface->GeLogin(KeysList, ValuesList,auth_level); + web_interface->GetLogin(KeysList, ValuesList,auth_level, !outputjson); //IP+Web web_interface->GetIpWeb(KeysList, ValuesList); //mode web_interface->GetMode(KeysList, ValuesList); - //page title and filenames - web_interface->SetPageProp(KeysList,ValuesList,FPSTR(VALUE_HOME),F("system")); - //menu item - KeysList.add(FPSTR(KEY_MENU_SYSTEM)); - ValuesList.add(FPSTR(VALUE_ACTIVE)); - + if (!outputjson){ + //page title and filenames + web_interface->SetPageProp(KeysList,ValuesList,FPSTR(VALUE_HOME),F("system")); + //menu item + KeysList.add(FPSTR(KEY_MENU_SYSTEM)); + ValuesList.add(FPSTR(VALUE_ACTIVE)); + } //check is it is a submission or a display if (web_interface->WebServer.hasArg("SUBMIT")) { //is there a correct list of values? @@ -1067,73 +1181,110 @@ void handle_web_interface_configSys() //Baud rate list istatus = 0; stmp=""; - while (lbaudlist[istatus]>-1) { - stmp+="