server_remote.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. #include "defines.h"
  2. #include "server_remote.h"
  3. #include "connect_wifi_ap.h"
  4. #include <string.h>
  5. #include <string>
  6. #include <freertos/FreeRTOS.h>
  7. #include <freertos/task.h>
  8. #include <esp_mac.h>
  9. #include <esp_wifi.h>
  10. #include <esp_log.h>
  11. #include <nvs_flash.h>
  12. #include <stdio.h>
  13. #include <lwip/err.h>
  14. #include <lwip/sys.h>
  15. #include "time_sntp.h"
  16. #include "ClassLogFile.h"
  17. #include "server_help.h"
  18. #include "Helper.h"
  19. #include "statusled.h"
  20. #include "server_ota.h"
  21. #include "basic_auth.h"
  22. static const char *TAG = "REMOTE SERVER";
  23. std::string remote_webserver_start_time = "";
  24. bool is_config_ini = false;
  25. bool is_wlan_ini = false;
  26. void remote_send_http_response(httpd_req_t *req)
  27. {
  28. std::string message = "<h1>AI-on-the-edge - BASIC SETUP</h1><p>This is an access point with a minimal server to setup the minimum required files and information on the device and the SD-card. ";
  29. message += "This mode is always started if one of the following files is missing: /wlan.ini or the /config/config.ini.<p>";
  30. message += "The setup is done in 3 steps: 1. upload full inital configuration (sd-card content), 2. store WLAN access information, 3. reboot (and connect to WLANs)<p><p>";
  31. message += "Please follow the below instructions.<p>";
  32. httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));
  33. is_wlan_ini = file_exists(NETWORK_CONFIG_FILE);
  34. if (!is_config_ini)
  35. {
  36. message = "<h3>1. Upload initial configuration to sd-card</h3><p>";
  37. message += "The configuration file config.ini is missing and most propably the full configuration and html folder on the sd-card. ";
  38. message += "This is normal after the first flashing of the firmware and an empty sd-card. Please upload \"remote_setup.zip\", which contains a full inital configuration.<p>";
  39. message += "<input id=\"newfile\" type=\"file\"><br>";
  40. message += "<button class=\"button\" style=\"width:300px\" id=\"doUpdate\" type=\"button\" onclick=\"upload()\">Upload File</button><p>";
  41. message += "The upload might take up to 60s. After a succesfull upload the page will be updated.";
  42. httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));
  43. message = "<script language=\"JavaScript\">";
  44. message += "function upload() {";
  45. message += "var xhttp = new XMLHttpRequest();";
  46. message += "xhttp.onreadystatechange = function() {if (xhttp.readyState == 4) {if (xhttp.status == 200) {location.reload();}}};";
  47. message += "var filePath = document.getElementById(\"newfile\").value.split(/[\\\\/]/).pop();";
  48. message += "var file = document.getElementById(\"newfile\").files[0];";
  49. message += "if (!file.name.includes(\"remote-setup\")){if (!confirm(\"The zip file name should contain '...remote-setup...'. Are you sure that you have downloaded the correct file?\"))return;};";
  50. message += "var upload_path = \"/upload/firmware/\" + filePath; xhttp.open(\"POST\", upload_path, true); xhttp.send(file);document.reload();";
  51. message += "document.getElementById(\"doUpdate\").disabled = true;}";
  52. message += "</script>";
  53. httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));
  54. return;
  55. }
  56. if (!is_wlan_ini)
  57. {
  58. message = "<h3>2. WLAN access credentials</h3><p>";
  59. message += "<table>";
  60. message += "<tr><td>WLAN-SSID</td><td><input type=\"text\" name=\"ssid\" id=\"ssid\"></td><td>SSID of the WLAN</td></tr>";
  61. message += "<tr><td>WLAN-Password</td><td><input type=\"text\" name=\"password\" id=\"password\"></td><td>ATTENTION: the password will not be encrypted during the sending.</td><tr>";
  62. message += "</table><p>";
  63. message += "<h4>ATTENTION:<h4>Be sure about the WLAN settings. They cannot be reset afterwards. If ssid or password is wrong, you need to take out the sd-card and manually change them in \"wlan.ini\"!<p>";
  64. httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));
  65. message = "<button class=\"button\" type=\"button\" onclick=\"wr()\">Write wlan.ini</button>";
  66. message += "<script language=\"JavaScript\">async function wr(){";
  67. message += "api = \"/config?\"+\"ssid=\"+document.getElementById(\"ssid\").value+\"&pwd=\"+document.getElementById(\"password\").value;";
  68. message += "fetch(api);await new Promise(resolve => setTimeout(resolve, 1000));location.reload();}</script>";
  69. httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));
  70. return;
  71. }
  72. message = "<h3>3. Reboot</h3><p>";
  73. message += "After triggering the reboot, the zip-files gets extracted and written to the sd-card.<br>The ESP32 will restart two times and then connect to your access point. Please find the IP in your router settings and access it with the new ip-address.<p>";
  74. message += "The first update and initialization process can take up to 3 minutes before you find it in the wlan. Error logs can be found on the console / serial logout.<p>Have fun!<p>";
  75. message += "<button class=\"button\" type=\"button\" onclick=\"rb()\")>Reboot to first setup.</button>";
  76. message += "<script language=\"JavaScript\">async function rb(){";
  77. message += "api = \"/reboot\";";
  78. message += "fetch(api);await new Promise(resolve => setTimeout(resolve, 1000));location.reload();}</script>";
  79. httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));
  80. }
  81. esp_err_t remote_test_handler(httpd_req_t *req)
  82. {
  83. remote_send_http_response(req);
  84. httpd_resp_send_chunk(req, NULL, 0);
  85. return ESP_OK;
  86. }
  87. esp_err_t remote_reboot_handler(httpd_req_t *req)
  88. {
  89. LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Trigger reboot due to firmware update.");
  90. doRebootOTA();
  91. return ESP_OK;
  92. }
  93. esp_err_t remote_config_ini_handler(httpd_req_t *req)
  94. {
  95. LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "config_ini_handler");
  96. char _query[400];
  97. char _valuechar[100];
  98. std::string fn = "/sdcard/firmware/";
  99. std::string _task = "";
  100. std::string ssid = "";
  101. std::string pwd = "";
  102. std::string hn = ""; // hostname
  103. std::string ip = "";
  104. std::string gw = ""; // gateway
  105. std::string nm = ""; // netmask
  106. std::string dns = "";
  107. std::string rssithreshold = ""; // rssi threshold for WIFI roaming
  108. std::string text = "";
  109. if (httpd_req_get_url_query_str(req, _query, 400) == ESP_OK)
  110. {
  111. ESP_LOGD(TAG, "Query: %s", _query);
  112. if (httpd_query_key_value(_query, "ssid", _valuechar, 100) == ESP_OK)
  113. {
  114. ESP_LOGD(TAG, "ssid is found: %s", _valuechar);
  115. ssid = url_decode(std::string(_valuechar));
  116. }
  117. if (httpd_query_key_value(_query, "pwd", _valuechar, 100) == ESP_OK)
  118. {
  119. ESP_LOGD(TAG, "pwd is found: %s", _valuechar);
  120. pwd = url_decode(std::string(_valuechar));
  121. }
  122. if (httpd_query_key_value(_query, "ssid", _valuechar, 100) == ESP_OK)
  123. {
  124. ESP_LOGD(TAG, "ssid is found: %s", _valuechar);
  125. ssid = url_decode(std::string(_valuechar));
  126. }
  127. if (httpd_query_key_value(_query, "hn", _valuechar, 100) == ESP_OK)
  128. {
  129. ESP_LOGD(TAG, "hostname is found: %s", _valuechar);
  130. hn = url_decode(std::string(_valuechar));
  131. }
  132. if (httpd_query_key_value(_query, "ip", _valuechar, 100) == ESP_OK)
  133. {
  134. ESP_LOGD(TAG, "ip is found: %s", _valuechar);
  135. ip = url_decode(std::string(_valuechar));
  136. }
  137. if (httpd_query_key_value(_query, "gw", _valuechar, 100) == ESP_OK)
  138. {
  139. ESP_LOGD(TAG, "gateway is found: %s", _valuechar);
  140. gw = url_decode(std::string(_valuechar));
  141. }
  142. if (httpd_query_key_value(_query, "nm", _valuechar, 100) == ESP_OK)
  143. {
  144. ESP_LOGD(TAG, "netmask is found: %s", _valuechar);
  145. nm = url_decode(std::string(_valuechar));
  146. }
  147. if (httpd_query_key_value(_query, "dns", _valuechar, 100) == ESP_OK)
  148. {
  149. ESP_LOGD(TAG, "dns is found: %s", _valuechar);
  150. dns = url_decode(std::string(_valuechar));
  151. }
  152. if (httpd_query_key_value(_query, "rssithreshold", _valuechar, 100) == ESP_OK)
  153. {
  154. ESP_LOGD(TAG, "rssithreshold is found: %s", _valuechar);
  155. rssithreshold = url_decode(std::string(_valuechar));
  156. }
  157. }
  158. FILE *configfilehandle = fopen(NETWORK_CONFIG_FILE, "w");
  159. text = ";++++++++++++++++++++++++++++++++++\n";
  160. text += "; AI on the edge - WLAN configuration\n";
  161. text += "; ssid: Name of WLAN network (mandatory), e.g. \"WLAN-SSID\"\n";
  162. text += "; password: Password of WLAN network (mandatory), e.g. \"PASSWORD\"\n\n";
  163. fputs(text.c_str(), configfilehandle);
  164. if (ssid.length())
  165. {
  166. ssid = "ssid = \"" + ssid + "\"\n";
  167. }
  168. else
  169. {
  170. ssid = "ssid = \"\"\n";
  171. }
  172. fputs(ssid.c_str(), configfilehandle);
  173. if (pwd.length())
  174. {
  175. pwd = "password = \"" + pwd + "\"\n";
  176. }
  177. else
  178. {
  179. pwd = "password = \"\"\n";
  180. }
  181. fputs(pwd.c_str(), configfilehandle);
  182. text = "\n;++++++++++++++++++++++++++++++++++\n";
  183. text += "; Hostname: Name of device in network\n";
  184. text += "; This parameter can be configured via WebUI configuration\n";
  185. text += "; Default: \"watermeter\", if nothing is configured\n\n";
  186. fputs(text.c_str(), configfilehandle);
  187. if (hn.length())
  188. {
  189. hn = "hostname = \"" + hn + "\"\n";
  190. }
  191. else
  192. {
  193. hn = ";hostname = \"watermeter\"\n";
  194. }
  195. fputs(hn.c_str(), configfilehandle);
  196. text = "\n;++++++++++++++++++++++++++++++++++\n";
  197. text += "; Fixed IP: If you like to use fixed IP instead of DHCP (default), the following\n";
  198. text += "; parameters needs to be configured: ip, gateway, netmask are mandatory, dns optional\n\n";
  199. fputs(text.c_str(), configfilehandle);
  200. if (ip.length())
  201. {
  202. ip = "ip = \"" + ip + "\"\n";
  203. }
  204. else
  205. {
  206. ip = ";ip = \"xxx.xxx.xxx.xxx\"\n";
  207. }
  208. fputs(ip.c_str(), configfilehandle);
  209. if (gw.length())
  210. {
  211. gw = "gateway = \"" + gw + "\"\n";
  212. }
  213. else
  214. {
  215. gw = ";gateway = \"xxx.xxx.xxx.xxx\"\n";
  216. }
  217. fputs(gw.c_str(), configfilehandle);
  218. if (nm.length())
  219. {
  220. nm = "netmask = \"" + nm + "\"\n";
  221. }
  222. else
  223. {
  224. nm = ";netmask = \"xxx.xxx.xxx.xxx\"\n";
  225. }
  226. fputs(nm.c_str(), configfilehandle);
  227. text = "\n;++++++++++++++++++++++++++++++++++\n";
  228. text += "; DNS server (optional, if no DNS is configured, gateway address will be used)\n\n";
  229. fputs(text.c_str(), configfilehandle);
  230. if (dns.length())
  231. {
  232. dns = "dns = \"" + dns + "\"\n";
  233. }
  234. else
  235. {
  236. dns = ";dns = \"xxx.xxx.xxx.xxx\"\n";
  237. }
  238. fputs(dns.c_str(), configfilehandle);
  239. text = "\n;++++++++++++++++++++++++++++++++++\n";
  240. text += "; WIFI Roaming:\n";
  241. text += "; Network assisted roaming protocol is activated by default\n";
  242. text += "; AP / mesh system needs to support roaming protocol 802.11k/v\n";
  243. text += ";\n";
  244. text += "; Optional feature (usually not neccessary):\n";
  245. text += "; RSSI Threshold for client requested roaming query (RSSI < RSSIThreshold)\n";
  246. text += "; Note: This parameter can be configured via WebUI configuration\n";
  247. text += "; Default: 0 = Disable client requested roaming query\n\n";
  248. fputs(text.c_str(), configfilehandle);
  249. if (rssithreshold.length())
  250. {
  251. rssithreshold = "RSSIThreshold = " + rssithreshold + "\n";
  252. }
  253. else
  254. {
  255. rssithreshold = "RSSIThreshold = 0\n";
  256. }
  257. fputs(rssithreshold.c_str(), configfilehandle);
  258. fflush(configfilehandle);
  259. fclose(configfilehandle);
  260. std::string zw = "ota without parameter - should not be the case!";
  261. httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
  262. httpd_resp_send(req, zw.c_str(), zw.length());
  263. ESP_LOGD(TAG, "end config.ini");
  264. return ESP_OK;
  265. }
  266. esp_err_t remote_upload_post_handler(httpd_req_t *req)
  267. {
  268. printf("Starting the post handler\n");
  269. make_dir("/sdcard/config");
  270. make_dir("/sdcard/firmware");
  271. make_dir("/sdcard/html");
  272. make_dir("/sdcard/img_tmp");
  273. make_dir("/sdcard/log");
  274. make_dir("/sdcard/demo");
  275. printf("After starting the post handler\n");
  276. LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "remote_upload_post_handler");
  277. char filepath[FILE_PATH_MAX];
  278. FILE *fd = NULL;
  279. const char *filename = get_path_from_uri(filepath, "/sdcard", req->uri + sizeof("/upload") - 1, sizeof(filepath));
  280. if (!filename)
  281. {
  282. httpd_resp_send_err(req, HTTPD_414_URI_TOO_LONG, "Filename too long");
  283. return ESP_FAIL;
  284. }
  285. printf("filepath: %s, filename: %s\n", filepath, filename);
  286. delete_file(std::string(filepath));
  287. fd = fopen(filepath, "w");
  288. if (!fd)
  289. {
  290. ESP_LOGE(TAG, "Failed to create file: %s", filepath);
  291. httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to create file");
  292. return ESP_FAIL;
  293. }
  294. ESP_LOGI(TAG, "Receiving file: %s...", filename);
  295. char buf[1024];
  296. int received;
  297. int remaining = req->content_len;
  298. printf("remaining: %d\n", remaining);
  299. while (remaining > 0)
  300. {
  301. ESP_LOGI(TAG, "Remaining size: %d", remaining);
  302. if ((received = httpd_req_recv(req, buf, MIN(remaining, 1024))) <= 0)
  303. {
  304. if (received == HTTPD_SOCK_ERR_TIMEOUT)
  305. {
  306. continue;
  307. }
  308. fclose(fd);
  309. unlink(filepath);
  310. ESP_LOGE(TAG, "File reception failed!");
  311. httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file");
  312. return ESP_FAIL;
  313. }
  314. if (received && (received != fwrite(buf, 1, received, fd)))
  315. {
  316. fclose(fd);
  317. unlink(filepath);
  318. ESP_LOGE(TAG, "File write failed!");
  319. httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to write file to storage");
  320. return ESP_FAIL;
  321. }
  322. remaining -= received;
  323. }
  324. fclose(fd);
  325. is_config_ini = true;
  326. FILE *pfile = fopen("/sdcard/update.txt", "w");
  327. std::string _s_zw = "/sdcard" + std::string(filename);
  328. fwrite(_s_zw.c_str(), strlen(_s_zw.c_str()), 1, pfile);
  329. fclose(pfile);
  330. ESP_LOGI(TAG, "File reception complete");
  331. httpd_resp_set_hdr(req, "Location", "/test");
  332. httpd_resp_set_status(req, "303 See Other");
  333. httpd_resp_set_hdr(req, "Location", "/test");
  334. httpd_resp_send_chunk(req, NULL, 0);
  335. ESP_LOGI(TAG, "Update page send out");
  336. return ESP_OK;
  337. }
  338. httpd_handle_t start_remote_webserver(void)
  339. {
  340. httpd_handle_t my_remote_webserver_httpd_handle = NULL;
  341. httpd_config_t my_remote_webserver_httpd_config = HTTPD_DEFAULT_CONFIG();
  342. remote_webserver_start_time = getCurrentTimeString("%Y%m%d-%H%M%S");
  343. my_remote_webserver_httpd_config.uri_match_fn = httpd_uri_match_wildcard;
  344. // Start the httpd server
  345. ESP_LOGI(TAG, "Starting server on port: '%d'", my_remote_webserver_httpd_config.server_port);
  346. if (httpd_start(&my_remote_webserver_httpd_handle, &my_remote_webserver_httpd_config) == ESP_OK)
  347. {
  348. // Set URI handlers
  349. ESP_LOGI(TAG, "Registering URI handlers");
  350. return my_remote_webserver_httpd_handle;
  351. }
  352. ESP_LOGI(TAG, "Error starting ap server!");
  353. return NULL;
  354. }
  355. void stop_remote_webserver(httpd_handle_t server)
  356. {
  357. httpd_stop(server);
  358. }
  359. httpd_handle_t remote_webserver_register_uri(httpd_handle_t server)
  360. {
  361. httpd_uri_t reboot_handle = {
  362. .uri = "/reboot", // Match all URIs of type /path/to/file
  363. .method = HTTP_GET,
  364. .handler = APPLY_BASIC_AUTH_FILTER(remote_reboot_handler),
  365. .user_ctx = NULL // Pass server data as context
  366. };
  367. httpd_register_uri_handler(server, &reboot_handle);
  368. httpd_uri_t config_ini_handle = {
  369. .uri = "/config", // Match all URIs of type /path/to/file
  370. .method = HTTP_GET,
  371. .handler = APPLY_BASIC_AUTH_FILTER(remote_config_ini_handler),
  372. .user_ctx = NULL // Pass server data as context
  373. };
  374. httpd_register_uri_handler(server, &config_ini_handle);
  375. /* URI handler for uploading files to server */
  376. httpd_uri_t file_uploadAP = {
  377. .uri = "/upload/*", // Match all URIs of type /upload/path/to/file
  378. .method = HTTP_POST,
  379. .handler = APPLY_BASIC_AUTH_FILTER(remote_upload_post_handler),
  380. .user_ctx = NULL // Pass server data as context
  381. };
  382. httpd_register_uri_handler(server, &file_uploadAP);
  383. httpd_uri_t test_uri = {
  384. .uri = "*",
  385. .method = HTTP_GET,
  386. .handler = APPLY_BASIC_AUTH_FILTER(remote_test_handler),
  387. .user_ctx = NULL};
  388. httpd_register_uri_handler(server, &test_uri);
  389. return NULL;
  390. }