Просмотр исходного кода

Merge branch 'jomjol:rolling' into rolling

Frank Haverland 3 лет назад
Родитель
Сommit
a167770848

+ 5 - 0
README.md

@@ -40,6 +40,11 @@ In other cases you can contact the developer via email: <img src="https://raw.gi
 
 ------
 
+##### Rolling (2022-09-04)
+
+- Improved Reboot (**[caco3](https://github.com/caco3)**)
+- HTML: Bug fix
+
 ##### Rolling (2022-09-03)
 
 - MQTT: improved handling based on the work of @**[caco3](https://github.com/caco3)** ([#971](https://github.com/jomjol/AI-on-the-edge-device/pull/971))

+ 1 - 1
code/components/jomjol_fileserver_ota/server_ota.cpp

@@ -437,7 +437,7 @@ esp_err_t handler_reboot(httpd_req_t *req)
 
     LogFile.WriteToFile("handler_reboot");
     ESP_LOGI(TAGPARTOTA, "!!! System will restart within 5 sec!!!");
-    const char* resp_str = "<body style='font-family: arial'> <h3 id=t></h3></body><script>var h='Rebooting!<br>The page will automatically reload.<br>'; document.getElementById('t').innerHTML=h; setInterval(function (){h +='.'; document.getElementById('t').innerHTML=h; fetch(window.location.hostname,{mode: 'no-cors'}).then(r=>{window.location.replace('/wasserzaehler_roi.html');})}, 1000);</script>";
+    const char* resp_str = "<body style='font-family: arial'> <h3 id=t></h3></body><script>var h='Rebooting!<br>The page will automatically reload after around 25s.<br>'; document.getElementById('t').innerHTML=h; setInterval(function (){h +='.'; document.getElementById('t').innerHTML=h; fetch(window.location.hostname,{mode: 'no-cors'}).then(r=>{parent.location.href=('/index.html');})}, 1000);</script>";
     httpd_resp_send(req, resp_str, strlen(resp_str)); 
     
     doReboot();

+ 5 - 39
code/components/jomjol_flowcontroll/ClassFlowCNNGeneral.cpp

@@ -340,38 +340,6 @@ int ClassFlowCNNGeneral::ZeigerEvalAnalogNeu(float zahl, int ziffer_vorgaenger)
 
 }
 
-/*
-int ClassFlowCNNGeneral::ZeigerEval(float zahl, int ziffer_vorgaenger)
-{   
-    int ergebnis_nachkomma = ((int) floor(zahl * 10) + 10) % 10;
-    int ergebnis_vorkomma = ((int) floor(zahl) + 10) % 10;
-    int ergebnis;
-    float ergebnis_rating;
-    if (debugdetailgeneral) LogFile.WriteToFile("ClassFlowCNNGeneral::ZeigerEval erg_v=" + std::to_string(ergebnis_vorkomma) + ", erg_n=" + std::to_string(ergebnis_nachkomma) + ", ziff_v=" + std::to_string(ziffer_vorgaenger));
-
-    if (ziffer_vorgaenger == -1)
-        return ergebnis_vorkomma % 10;
-
-    // Ist die aktuelle Stelle schon umgesprungen und die Vorstelle noch nicht?
-    // Akt.: 2.1, Vorstelle = 0.9 => 1.9
-    // Problem sind mehrere Rundungen 
-    // Bsp. zahl=4.5, Vorgänger= 9.6 (ziffer_vorgaenger=0)
-    // Tritt nur auf bei Übergang von analog auf digit
-    ergebnis_rating = ergebnis_nachkomma - ziffer_vorgaenger;
-    if (ergebnis_nachkomma >= 5)
-        ergebnis_rating-=5.1;
-    else
-        ergebnis_rating+=5;
-    ergebnis = (int) round(zahl);
-    if (ergebnis_rating < 0)
-        ergebnis-=1;
-    if (ergebnis == -1)
-        ergebnis+=10;
-    
-    ergebnis = (ergebnis + 10) % 10;
-    return ergebnis;
-}
-*/
 
 bool ClassFlowCNNGeneral::ReadParameter(FILE* pfile, string& aktparamgraph)
 {
@@ -417,11 +385,6 @@ bool ClassFlowCNNGeneral::ReadParameter(FILE* pfile, string& aktparamgraph)
         {
             this->logfileRetentionInDays = std::stoi(zerlegt[1]);
         }
-//        if ((toUpper(zerlegt[0]) == "MODELTYPE") && (zerlegt.size() > 1))
-//        {
-//            if (toUpper(zerlegt[1]) == "DIGITHYPRID")
-//                CNNType = DigitalHyprid;
-//        }
 
         if ((toUpper(zerlegt[0]) == "MODEL") && (zerlegt.size() > 1))
         {
@@ -664,10 +627,11 @@ bool ClassFlowCNNGeneral::getNetworkParameter()
                 CNNType = Digital;
                 printf("TFlite-Type set to Digital\n");
                 break;
-            case 20:
+/*            case 20:
                 CNNType = DigitalHyprid10;
                 printf("TFlite-Type set to DigitalHyprid10\n");
                 break;
+*/
 //            case 22:
 //                CNNType = DigitalHyprid;
 //                printf("TFlite-Type set to DigitalHyprid\n");
@@ -801,6 +765,7 @@ bool ClassFlowCNNGeneral::doNeuralNetwork(string time)
                         }
                     } break;
 */
+/*
                 case DigitalHyprid10:
                     {
                         int _num, _nachkomma;
@@ -836,6 +801,7 @@ bool ClassFlowCNNGeneral::doNeuralNetwork(string time)
                             }
                         }
                     } break;
+*/
 
                 case DoubleHyprid10:
                     {
@@ -869,7 +835,7 @@ bool ClassFlowCNNGeneral::doNeuralNetwork(string time)
                             _fit = _val + _valminus;
 
                         }
-                        if (result > 10)
+                        if (result >= 10)
                             result = result - 10;
                         if (result < 0)
                             result = result + 10;

+ 1 - 4
code/components/jomjol_flowcontroll/ClassFlowCNNGeneral.h

@@ -37,12 +37,9 @@ protected:
     bool isLogImageSelect;
     string LogImageSelect;
     ClassFlowAlignment* flowpostalignment;
-//    ClassFlowPostProcessing *flowpostprocessing = NULL;
+
     bool SaveAllFiles;   
-//    bool extendedResolution;
 
-//    int ZeigerEval(float zahl, int ziffer_vorgaenger);
-//    int ZeigerEvalHybrid(float zahl, float zahl_vorgaenger, int eval_vorgaenger);
     int ZeigerEvalAnalogNeu(float zahl, int ziffer_vorgaenger);
     int ZeigerEvalAnalogToDigitNeu(float zahl, float ziffer_vorgaenger,  int eval_vorgaenger);
     int ZeigerEvalHybridNeu(float zahl, float zahl_vorgaenger, int eval_vorgaenger, bool AnalogerVorgaenger = false);

+ 5 - 0
code/components/jomjol_flowcontroll/ClassFlowImage.cpp

@@ -63,7 +63,12 @@ void ClassFlowImage::LogImage(string logPath, string name, float *resultFloat, i
         if (*resultFloat < 0)
             sprintf(buf, "N.N_");
         else
+        {
             sprintf(buf, "%.1f_", *resultFloat);
+            if (strcmp(buf, "10.0_"))
+                sprintf(buf, "0.0_");
+        }
+            
 	} else if (resultInt != NULL) {
 		sprintf(buf, "%d_", *resultInt);
 	} else {

+ 86 - 27
code/components/jomjol_flowcontroll/ClassFlowMQTT.cpp

@@ -32,9 +32,7 @@ void ClassFlowMQTT::SetInitialParameter(void)
     ListFlowControll = NULL; 
     disabled = false;
     MQTTenable = false;
-    
-    
-
+    keepAlive = 600; // TODO This must be greater than the Flow Interval!
 }       
 
 ClassFlowMQTT::ClassFlowMQTT()
@@ -125,15 +123,50 @@ bool ClassFlowMQTT::ReadParameter(FILE* pfile, string& aktparamgraph)
         printf("InitMQTTInit\n");
         mainerrortopic = maintopic + "/connection";
         printf("Init MQTT with uri: %s, clientname: %s, user: %s, password: %s, maintopic: %s\n", uri.c_str(), clientname.c_str(), user.c_str(), password.c_str(), mainerrortopic.c_str());
-        MQTTInit(uri, clientname, user, password, mainerrortopic, 60); 
-        if (MQTTPublish(mainerrortopic, "connected", SetRetainFlag)) {
-            MQTTenable = true;
-        }
-        else {
-            MQTTenable = true;
+        if (!MQTTInit(uri, clientname, user, password, mainerrortopic, keepAlive))
+        { // Failed
+            MQTTenable = false;
+            return true; // We need to return true despite we failed, else it will retry 5x and then reboot!
         }
     }
+
+    // Try sending mainerrortopic. If it fails, re-run init
+    if (!MQTTPublish(mainerrortopic, "connected", SetRetainFlag))
+    { // Failed
+        LogFile.WriteToFile("MQTT - Re-running init...!");
+        if (!MQTTInit(this->uri, this->clientname, this->user, this->password, this->mainerrortopic, keepAlive))
+        { // Failed
+            MQTTenable = false;
+            return false;
+        } 
+    }
+
+    // Try again and quit if it fails
+    if (!MQTTPublish(mainerrortopic, "connected", SetRetainFlag))
+    { // Failed
+        MQTTenable = false;
+        return false;
+    }
+
+
+
    
+ /*   if (!MQTTPublish(mainerrortopic, "connected", SetRetainFlag))
+    { // Failed
+        LogFile.WriteToFile("MQTT - Could not publish connection status!");
+        MQTTenable = false;
+        return true; // We need to return true despite we failed, else it will retry 5x and then reboot!
+    }*/
+
+ /*   if(!MQTTPublish(_LWTContext, "", 1))
+    {
+        LogFile.WriteToFile("MQTT - Could not publish LWT!");
+        MQTTenable = false;
+        return true; // We need to return true despite we failed, else it will retry 5x and then reboot!
+    }*/
+
+
+    MQTTenable = true;
     return true;
 }
 
@@ -146,18 +179,43 @@ string ClassFlowMQTT::GetMQTTMainTopic()
 
 bool ClassFlowMQTT::doFlow(string zwtime)
 {
-    if (!MQTTenable) {
-        LogFile.WriteToFile("MQTT not enabled!");
-
-        // Try again to init it
-        MQTTInit(this->uri, this->clientname, this->user, this->password, this->mainerrortopic, 60); 
-        if (MQTTPublish(mainerrortopic, "connected", SetRetainFlag)) {
-            MQTTenable = true;
-        }
-        else { // Failed
+  //  if (!MQTTenable) {
+  //      LogFile.WriteToFile("MQTT not enabled!");
+  //
+  //      // Try again to init it
+  //   if (!MQTTInit(this->uri, this->clientname, this->user, this->password, this->mainerrortopic, keepAlive))
+  //      { // Failed
+  //          MQTTenable = false;
+  //          return true; // We need to return true despite we failed, else it will retry 5x and then reboot!
+  //      } 
+  //
+  //     if (!MQTTPublish(mainerrortopic, "connected", SetRetainFlag))
+  //      { // Failed
+  //          MQTTenable = false;
+  //          return true; // We need to return true despite we failed, else it will retry 5x and then reboot!
+  //      }
+  //      
+  //      LogFile.WriteToFile("MQTT is now enabled");
+  //      MQTTenable = true;
+  //  }
+
+
+    // Try sending mainerrortopic. If it fails, re-run init
+    if (!MQTTPublish(mainerrortopic, "connected", SetRetainFlag))
+    { // Failed
+        LogFile.WriteToFile("MQTT - Re-running init...!");
+        if (!MQTTInit(this->uri, this->clientname, this->user, this->password, this->mainerrortopic, keepAlive))
+        { // Failed
+            MQTTenable = false;
             return true; // We need to return true despite we failed, else it will retry 5x and then reboot!
-        }
-        LogFile.WriteToFile("MQTT is now enabled");
+        } 
+    }
+
+    // Try again and quit if it fails
+    if (!MQTTPublish(mainerrortopic, "connected", SetRetainFlag))
+    { // Failed
+        MQTTenable = false;
+        return true; // We need to return true despite we failed, else it will retry 5x and then reboot!
     }
 
     std::string result;
@@ -169,12 +227,10 @@ bool ClassFlowMQTT::doFlow(string zwtime)
     string zw = "";
     string namenumber = "";
 
-    if (MQTTPublish(mainerrortopic, "connected")) {
-        MQTTenable = true;
-    }
-    else { // Failed, skip other topics
-        return true; // We need to return true despite we failed, else it will retry 5x and then reboot!
-    }
+    // if (!MQTTPublish(mainerrortopic, "connected", SetRetainFlag))
+    //{ // Failed, skip other topics
+    //    return true; // We need to return true despite we failed, else it will retry 5x and then reboot!
+    //}
     
     zw = maintopic + "/" + "uptime";
     char uptimeStr[11];
@@ -184,7 +240,10 @@ bool ClassFlowMQTT::doFlow(string zwtime)
     zw = maintopic + "/" + "freeMem";
     char freeheapmem[11];
     sprintf(freeheapmem, "%zu", esp_get_free_heap_size());
-    MQTTPublish(zw, freeheapmem, SetRetainFlag);
+    if (!MQTTPublish(zw, freeheapmem, SetRetainFlag))
+    { // Failed, skip other topics
+        return true; // We need to return true despite we failed, else it will retry 5x and then reboot!
+    }
 
     zw = maintopic + "/" + "wifiRSSI";
     char rssi[11];

+ 1 - 0
code/components/jomjol_flowcontroll/ClassFlowMQTT.h

@@ -15,6 +15,7 @@ protected:
     std::string user, password; 
     int SetRetainFlag;
     bool MQTTenable;
+    int keepAlive;
 
     std::string maintopic, mainerrortopic; 
 	void SetInitialParameter(void);        

+ 39 - 15
code/components/jomjol_mqtt/interface_mqtt.cpp

@@ -20,22 +20,36 @@ bool mqtt_connected = false;
 esp_mqtt_client_handle_t client = NULL;
 
 bool MQTTPublish(std::string _key, std::string _content, int retained_flag){
-    if (!client) {
-        LogFile.WriteToFile("MQTT - client not initialized!");  
-        return false;      
+  
+  //  if (!client) {
+  //      LogFile.WriteToFile("MQTT - client not initialized!");  
+  //      return false;      
+  //  }
+  //  LogFile.WriteToFile("MQTT - client initialized!");  // Debug
+  //
+  //  if (!mqtt_connected) {
+  //      LogFile.WriteToFile("MQTT - Can not publish, not connected!");
+  //      ESP_LOGW(TAG_INTERFACEMQTT, "Problem with Publish, client=%d, mqtt_connected %d", (int) client, (int) mqtt_connected);
+  //      return false;            
+  //  }
+  //  LogFile.WriteToFile("MQTT - connected!");  // Debug
+
+ /*   if (client && mqtt_connected) {
+        LogFile.WriteToFile("MQTT - connected!");  // Debug
     }
+    else { // init needed
+        if (!MQTTInit(this->uri, this->clientname, this->user, password, mainerrortopic, keepAlive)) // validate{
+        { // Failed
+            return false;
+        }
+    }*/
 
-    if (!mqtt_connected) {
-        LogFile.WriteToFile("MQTT - Can not publish, not connected!");
-        ESP_LOGW(TAG_INTERFACEMQTT, "Problem with Publish, client=%d, mqtt_connected %d", (int) client, (int) mqtt_connected);
-        return false;            
-    }
 
     int msg_id;
     std::string zw;
     msg_id = esp_mqtt_client_publish(client, _key.c_str(), _content.c_str(), 0, 1, retained_flag);
     if (msg_id < 0) {
-        LogFile.WriteToFile("MQTT - Failed to publish + " + _key + ", no connection!");
+        LogFile.WriteToFile("MQTT - Failed to publish '" + _key + "'!");
         return false;
     }
     zw = "MQTT - sent publish successful in MQTTPublish, msg_id=" + std::to_string(msg_id) + ", " + _key + ", " + _content;
@@ -102,7 +116,7 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_
 }
 
 
-void MQTTInit(std::string _mqttURI, std::string _clientid, std::string _user, std::string _password, std::string _LWTContext, int _keepalive){
+bool MQTTInit(std::string _mqttURI, std::string _clientid, std::string _user, std::string _password, std::string _LWTContext, int _keepalive){
     std::string _zwmessage = "connection lost";
 
     int _lzw = _zwmessage.length();
@@ -141,20 +155,30 @@ void MQTTInit(std::string _mqttURI, std::string _clientid, std::string _user, st
     if (client)
     {
         if (esp_mqtt_client_register_event(client, esp_mmqtt_ID, mqtt_event_handler, client) != ESP_OK)
+        {
             LogFile.WriteToFile("MQTT - Could not register event!");
+            return false;
+        }
         if (esp_mqtt_client_start(client) != ESP_OK)
+        {
             LogFile.WriteToFile("MQTT - Could not start client!");
-
-        if(MQTTPublish(_LWTContext, "", 1)) {
-            LogFile.WriteToFile("MQTT - Client init successful");
+            return false;
         }
+
+       /* if(!MQTTPublish(_LWTContext, "", 1))
+        {
+            LogFile.WriteToFile("MQTT - Could not publish LWT!");
+            return false;
+        }*/
     }
     else
     {
         LogFile.WriteToFile("MQTT - Could not Init client!");
+        return false;
     }
 
-
+    LogFile.WriteToFile("MQTT - Init successful");
+    return true;
 }
 
 /*
@@ -256,7 +280,7 @@ void MQTTconnected(){
             }
         }
 
-        if (subscribeFunktionMap != NULL) {
+       if (subscribeFunktionMap != NULL) {
             for(std::map<std::string, std::function<bool(std::string, char*, int)>>::iterator it = subscribeFunktionMap->begin(); it != subscribeFunktionMap->end(); ++it) {
                 int msg_id = esp_mqtt_client_subscribe(client, it->first.c_str(), 0);
                 ESP_LOGD(TAG_INTERFACEMQTT, "topic %s subscribe successful, msg_id=%d", it->first.c_str(), msg_id);

+ 1 - 1
code/components/jomjol_mqtt/interface_mqtt.h

@@ -5,7 +5,7 @@
 #include <map>
 #include <functional>
 
-void MQTTInit(std::string _mqttURI, std::string _clientid, std::string _user, std::string _password, std::string _LWTContext, int _keepalive);
+bool MQTTInit(std::string _mqttURI, std::string _clientid, std::string _user, std::string _password, std::string _LWTContext, int _keepalive);
 void MQTTdestroy();
 
 //void MQTTInit(std::string _mqttURI, std::string _clientid, std::string _user = "", std::string _password = "");

+ 2 - 2
code/main/version.cpp

@@ -1,4 +1,4 @@
-const char* GIT_REV="77427a8";
+const char* GIT_REV="47da2d6";
 const char* GIT_TAG="";
 const char* GIT_BRANCH="rolling";
-const char* BUILD_TIME="2022-09-03 08:15";
+const char* BUILD_TIME="2022-09-04 18:01";

+ 2 - 2
code/version.cpp

@@ -1,4 +1,4 @@
-const char* GIT_REV="77427a8";
+const char* GIT_REV="47da2d6";
 const char* GIT_TAG="";
 const char* GIT_BRANCH="rolling";
-const char* BUILD_TIME="2022-09-03 08:15";
+const char* BUILD_TIME="2022-09-04 18:01";

BIN
firmware/bootloader.bin


BIN
firmware/firmware.bin


BIN
firmware/html.zip


+ 104 - 1
sd-card/html/index.html

@@ -5,7 +5,110 @@
 <title>jomjol - AI on the edge</title>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
-<link rel="stylesheet" href="style.css" type="text/css" >
+<!-- <link rel="stylesheet" href="style.css" type="text/css" > -->
+<style>
+/* Older Firmware versions (11.2.0 and older) do not support css files, therefore we need to keep it integrated for now! */
+body, html {
+    width: 100%; 
+    height: 100%; 
+    min-height: 800px;
+    margin: 0px 0px 0px 2px; 
+    padding: 0; 
+    font-family: arial;
+    width: fit-content;
+}
+
+.main {
+    display: flex; 
+    width: 100%; 
+    height: 100%; 
+    flex-direction: column; 
+    overflow: hidden;
+}
+
+.iframe {
+    flex-grow: 1;
+    margin: 5px 7px 4px 0px; 
+    padding: 0; 
+    border: 2px solid black;
+}
+
+h1 {
+    font-size: 2em; 
+    margin-block-end: 0.3em;
+}
+
+h2 {
+    font-size: 1.5em;
+    margin-block-start: 0.3em;
+}
+
+h3 {
+    font-size: 1.2em;
+}
+
+p {
+    font-size: 1em;
+}
+
+ul {
+    list-style-type: none;
+    margin: 0;
+    padding: 0;
+    overflow: hidden;
+    background-color: #333;
+    width:1000px;
+}
+
+li {
+    float: left;
+    font-family: arial;
+    font-size: 18px;
+}
+
+li a, .dropbtn {
+    display: inline-block;
+    color: white;
+    text-align: center;
+    padding: 14px 16px;
+    text-decoration: none;
+}
+
+li a:hover, .dropdown:hover .dropbtn {
+    background-color: red;
+}
+
+li.dropdown {
+    display: inline-block;
+}
+
+.dropdown-content {
+    display: none;
+    position: absolute;
+    background-color: #f9f9f9;
+    min-width: 160px;
+    box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
+    z-index: 1;
+    font-family: arial;
+}
+
+.dropdown-content a {
+    color: black;
+    padding: 12px 16px;
+    text-decoration: none;
+    display: block;
+    text-align: left;
+}
+
+.dropdown-content a:hover {
+    color: white;
+    background-color: red;
+}
+
+.dropdown:hover .dropdown-content {
+    display: block;
+} 
+</style>
 
 <script type="text/javascript" src="common.js"></script>
 <script type="text/javascript" src="gethost.js"></script>

+ 105 - 1
sd-card/html/index_configure.html

@@ -5,7 +5,110 @@
 <title>jomjol - AI on the edge</title>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
-<link rel="stylesheet" href="style.css" type="text/css" >
+<!-- <link rel="stylesheet" href="style.css" type="text/css" > -->
+<style>
+/* Older Firmware versions (11.2.0 and older) do not support css files, therefore we need to keep it integrated for now! */
+body, html {
+    width: 100%; 
+    height: 100%; 
+    min-height: 800px;
+    margin: 0px 0px 0px 2px; 
+    padding: 0; 
+    font-family: arial;
+    width: fit-content;
+}
+
+.main {
+    display: flex; 
+    width: 100%; 
+    height: 100%; 
+    flex-direction: column; 
+    overflow: hidden;
+}
+
+.iframe {
+    flex-grow: 1;
+    margin: 5px 7px 4px 0px; 
+    padding: 0; 
+    border: 2px solid black;
+}
+
+h1 {
+    font-size: 2em; 
+    margin-block-end: 0.3em;
+}
+
+h2 {
+    font-size: 1.5em;
+    margin-block-start: 0.3em;
+}
+
+h3 {
+    font-size: 1.2em;
+}
+
+p {
+    font-size: 1em;
+}
+
+ul {
+    list-style-type: none;
+    margin: 0;
+    padding: 0;
+    overflow: hidden;
+    background-color: #333;
+    width:1000px;
+}
+
+li {
+    float: left;
+    font-family: arial;
+    font-size: 18px;
+}
+
+li a, .dropbtn {
+    display: inline-block;
+    color: white;
+    text-align: center;
+    padding: 14px 16px;
+    text-decoration: none;
+}
+
+li a:hover, .dropdown:hover .dropbtn {
+    background-color: red;
+}
+
+li.dropdown {
+    display: inline-block;
+}
+
+.dropdown-content {
+    display: none;
+    position: absolute;
+    background-color: #f9f9f9;
+    min-width: 160px;
+    box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
+    z-index: 1;
+    font-family: arial;
+}
+
+.dropdown-content a {
+    color: black;
+    padding: 12px 16px;
+    text-decoration: none;
+    display: block;
+    text-align: left;
+}
+
+.dropdown-content a:hover {
+    color: white;
+    background-color: red;
+}
+
+.dropdown:hover .dropdown-content {
+    display: block;
+} 
+</style>
 </head>
 
 <body>
@@ -38,6 +141,7 @@
     <li class="dropdown">
 	<a href="javascript:void(0)" class="dropbtn">System</a>
 	<div class="dropdown-content">
+	    <a href="#"onclick="document.getElementById('maincontent').src = '/backup.html';">Backup/Restore</a>
 	    <a href="#"onclick="document.getElementById('maincontent').src = '/ota_page.html';">OTA Update</a>
 	    <a href="#"onclick="document.getElementById('maincontent').src = '/fileserver/log/message/?readonly=true';">Log Viewer</a>      
 	    <a href="#"onclick="document.getElementById('maincontent').src = '/reboot_page.html';">Reboot</a>

+ 1 - 1
sd-card/html/version.txt

@@ -1 +1 @@
-16.3.2
+16.3.4