|
| 1 | +/* |
| 2 | + This example shows how to retrieve data from Sqlite3 databases from SD Card |
| 3 | + through the Web Server and display in the form of HTML page. |
| 4 | + It also demonstrates query filtering by parameter passing and chunked encoding. |
| 5 | + Before running please copy 'so_users.db' to SD Card. Please see |
| 6 | +
|
| 7 | + https://github.com/siara-cc/stackoverflow_db |
| 8 | +
|
| 9 | + to find out how to obtain so_users.db |
| 10 | +
|
| 11 | + Please see https://github.com/siara-cc/esp32_arduino_sqlite3_lib/ |
| 12 | + for more inforemation. |
| 13 | +
|
| 14 | + Copyright (c) 2018, Siara Logics (cc) |
| 15 | +*/ |
| 16 | + |
| 17 | +/* |
| 18 | +* Copyright (c) 2015, Majenko Technologies |
| 19 | +* All rights reserved. |
| 20 | +* |
| 21 | +* Redistribution and use in source and binary forms, with or without modification, |
| 22 | +* are permitted provided that the following conditions are met: |
| 23 | +* |
| 24 | +* * Redistributions of source code must retain the above copyright notice, this |
| 25 | +* list of conditions and the following disclaimer. |
| 26 | +* |
| 27 | +* * Redistributions in binary form must reproduce the above copyright notice, this |
| 28 | +* list of conditions and the following disclaimer in the documentation and/or |
| 29 | +* other materials provided with the distribution. |
| 30 | +* |
| 31 | +* * Neither the name of Majenko Technologies nor the names of its |
| 32 | +* contributors may be used to endorse or promote products derived from |
| 33 | +* this software without specific prior written permission. |
| 34 | +* |
| 35 | +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| 36 | +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 37 | +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 38 | +* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR |
| 39 | +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 40 | +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 41 | +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| 42 | +* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 43 | +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 44 | +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 45 | +*/ |
| 46 | + |
| 47 | +#include <WiFi.h> |
| 48 | +#include <WiFiClient.h> |
| 49 | +#include <WebServer.h> |
| 50 | +#include <ESPmDNS.h> |
| 51 | +#include <sqlite3.h> |
| 52 | +#include <FS.h> |
| 53 | +#include "SD_MMC.h" |
| 54 | + |
| 55 | +const char *ssid = "Nokia1"; |
| 56 | +const char *password = "nokiafour"; |
| 57 | + |
| 58 | +WebServer server(80); |
| 59 | + |
| 60 | +const int led = 13; |
| 61 | + |
| 62 | +void handleRoot() { |
| 63 | + digitalWrite ( led, 1 ); |
| 64 | + String temp; |
| 65 | + int sec = millis() / 1000; |
| 66 | + int min = sec / 60; |
| 67 | + int hr = min / 60; |
| 68 | + |
| 69 | + temp = F("<html><head>\ |
| 70 | + <title>ESP32 Demo to Query database on Micro SD</title>\ |
| 71 | + <style>\ |
| 72 | + body { font-family: Arial, Helvetica, Sans-Serif; font-size: large; Color: #000088; }\ |
| 73 | + </style>\ |
| 74 | + </head>\ |
| 75 | + <body>\ |
| 76 | + <h1>Hello from ESP32!</h1>\ |
| 77 | + <h2>Query StackOverflow Users database on Micro SD Card</h2>\ |
| 78 | + <p>StackOverflow publishes snapshot of its data periodically at archive.org \ |
| 79 | + <a href='https://archive.org/download/stackexchange'>here</a> \ |
| 80 | + and is <a href='https://ia800107.us.archive.org/27/items/stackexchange/license.txt'> \ |
| 81 | + licensed under cc-by-sa 3.0</a>.</p>\ |
| 82 | + <p><a href='https://github.com/siara-cc/stackoverflow_db'>This repository</a> \ |
| 83 | + hosts StackOverflow User data imported into a convenient SQLite database\ |
| 84 | + (size 1.94 GB and contains close to 10 million records). The date of snapshot is 3-Dec-2018.</p>\ |
| 85 | + <p>This example shows how to retrieve data from this database copied to Micro SD Card \ |
| 86 | + attached to ESP32 through its Web Server and display in the form of HTML page.</p>\ |
| 87 | + <h3>Query by User Id</h3>\ |
| 88 | + <form name='params' method='GET' action='query_db'>\ |
| 89 | + Enter id: <input type=text style='font-size: large' value='5072621' name='so_id'/> \ |
| 90 | + <input type=hidden value='' name='so_disp_name'/> \ |
| 91 | + <p>(To find your id, see search box after clicking your profile icon on https://stackoverflow.com)</p>\ |
| 92 | + <input type=submit style='font-size: large' value='Query database by Id'/>\ |
| 93 | + </form>\ |
| 94 | + <hr>\ |
| 95 | + <h3>Query by Display name</h3>\ |
| 96 | + <form name='params' method='GET' action='query_db'>\ |
| 97 | + <input type=hidden value='' name='so_id'/> \ |
| 98 | + Enter Display name: <input type=text style='font-size: large' value='roadrunner' name='so_disp_name'/> \ |
| 99 | + <br><br><input type=submit style='font-size: large' value='Query database by Display Name'/>\ |
| 100 | + </form>\ |
| 101 | + <hr>\ |
| 102 | + <h3>Aggregate Query by Location</h3>\ |
| 103 | + <form name='params' method='GET' action='query_db'>\ |
| 104 | + <input type=hidden value='' name='so_id'/> \ |
| 105 | + <input type=hidden value='' name='so_disp_name'/> \ |
| 106 | + Enter Location (blank for all): \ |
| 107 | + <input type=text style='font-size: large' value='' name='so_loc'/> \ |
| 108 | + <br>Minimum count:\ |
| 109 | + <input type=text style='font-size: large' value='10000' name='so_loc_count'/> \ |
| 110 | + <br><br><input type=submit style='font-size: large' value='Aggregate Query by Location'/>\ |
| 111 | + </form>\ |
| 112 | + <hr>\ |
| 113 | + </body>\ |
| 114 | + </html>"); |
| 115 | + server.send(200, "text/html", temp.c_str()); |
| 116 | + digitalWrite(led, 0); |
| 117 | +} |
| 118 | + |
| 119 | +void handleNotFound() { |
| 120 | + digitalWrite(led, 1); |
| 121 | + String message = F("File Not Found\n\n"); |
| 122 | + message += F("URI: "); |
| 123 | + message += server.uri(); |
| 124 | + message += F("\nMethod: "); |
| 125 | + message += (server.method() == HTTP_GET) ? "GET" : "POST"; |
| 126 | + message += F("\nArguments: "); |
| 127 | + message += server.args(); |
| 128 | + message += F("\n"); |
| 129 | + for ( uint8_t i = 0; i < server.args(); i++ ) { |
| 130 | + message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; |
| 131 | + } |
| 132 | + server.send(404, "text/plain", message); |
| 133 | + digitalWrite(led, 0); |
| 134 | +} |
| 135 | + |
| 136 | +sqlite3 *db1; |
| 137 | +int rc; |
| 138 | +sqlite3_stmt *res; |
| 139 | +int rec_count = 0; |
| 140 | +const char *tail; |
| 141 | + |
| 142 | +int openDb(const char *filename, sqlite3 **db) { |
| 143 | + int rc = sqlite3_open(filename, db); |
| 144 | + if (rc) { |
| 145 | + Serial.print(F("Can't open database: ")); |
| 146 | + Serial.println(sqlite3_errmsg(*db)); |
| 147 | + return rc; |
| 148 | + } else { |
| 149 | + Serial.println(F("Opened database successfully")); |
| 150 | + } |
| 151 | + return rc; |
| 152 | +} |
| 153 | + |
| 154 | +void setup(void) { |
| 155 | + pinMode(led, OUTPUT); |
| 156 | + digitalWrite(led, 0); |
| 157 | + Serial.begin(115200); |
| 158 | + WiFi.mode(WIFI_STA); |
| 159 | + WiFi.begin(ssid, password); |
| 160 | + Serial.println(F("Hello")); |
| 161 | + |
| 162 | + // Wait for connection |
| 163 | + while (WiFi.status() != WL_CONNECTED) { |
| 164 | + delay(500); |
| 165 | + Serial.print(F(".")); |
| 166 | + } |
| 167 | + |
| 168 | + Serial.println(""); |
| 169 | + Serial.print(F("Connected to ")); |
| 170 | + Serial.println(ssid); |
| 171 | + Serial.print(F("IP address: ")); |
| 172 | + Serial.println(WiFi.localIP()); |
| 173 | + |
| 174 | + if (MDNS.begin("esp32")) { |
| 175 | + Serial.println(F("MDNS responder started")); |
| 176 | + } |
| 177 | + |
| 178 | + SD_MMC.begin(); |
| 179 | + sqlite3_initialize(); |
| 180 | + |
| 181 | + // Open database |
| 182 | + if (openDb("/sdcard/so_users.db", &db1)) |
| 183 | + return; |
| 184 | + |
| 185 | + server.on("/", handleRoot); |
| 186 | + server.on("/query_db", []() { |
| 187 | + long start = micros(); |
| 188 | + String sql = F("Select Count(*) From SO_Users Where "); |
| 189 | + if (server.arg("so_disp_name").length() > 0) { |
| 190 | + sql += F("DisplayName = '"); |
| 191 | + sql += server.arg("so_disp_name"); |
| 192 | + sql += "'"; |
| 193 | + } else if (server.arg("so_id").length() > 0) { |
| 194 | + sql += F("Id = '"); |
| 195 | + sql += server.arg("so_id"); |
| 196 | + sql += F("'"); |
| 197 | + } else { |
| 198 | + sql = ""; |
| 199 | + } |
| 200 | + int step_res; |
| 201 | + if (sql.length() > 0) { |
| 202 | + rc = sqlite3_prepare_v2(db1, sql.c_str(), 1000, &res, &tail); |
| 203 | + if (rc != SQLITE_OK) { |
| 204 | + String resp = F("Failed to fetch data: "); |
| 205 | + resp += sqlite3_errmsg(db1); |
| 206 | + resp += F(".<br><br><input type=button onclick='location.href=\"/\"' value='back'/>"); |
| 207 | + server.send ( 200, "text/html", resp.c_str()); |
| 208 | + Serial.println(resp.c_str()); |
| 209 | + return; |
| 210 | + } |
| 211 | + do { |
| 212 | + step_res = sqlite3_step(res); |
| 213 | + if (step_res == SQLITE_ROW) { |
| 214 | + rec_count = sqlite3_column_int(res, 0); |
| 215 | + if (rec_count > 5000) { |
| 216 | + String resp = F("Too many records: "); |
| 217 | + resp += rec_count; |
| 218 | + resp += F(". Please select different range"); |
| 219 | + resp += F(".<br><br><input type=button onclick='location.href=\"/\"' value='back'/>"); |
| 220 | + server.send ( 200, "text/html", resp.c_str()); |
| 221 | + Serial.println(resp.c_str()); |
| 222 | + sqlite3_finalize(res); |
| 223 | + return; |
| 224 | + } |
| 225 | + } |
| 226 | + } while (step_res != SQLITE_DONE && step_res != SQLITE_ERROR); |
| 227 | + sqlite3_finalize(res); |
| 228 | + } else |
| 229 | + rec_count = -1; |
| 230 | + |
| 231 | + sql = F("Select * From SO_Users Where "); |
| 232 | + if (server.arg("so_disp_name").length() > 0) { |
| 233 | + sql += F("DisplayName = '"); |
| 234 | + sql += server.arg("so_disp_name"); |
| 235 | + sql += F("'"); |
| 236 | + } else if (server.arg("so_id").length() > 0) { |
| 237 | + sql += F("Id = '"); |
| 238 | + sql += server.arg("so_id"); |
| 239 | + sql += F("'"); |
| 240 | + } else { |
| 241 | + sql = F("Select Location, Count(*) Count From SO_Users "); |
| 242 | + if (server.arg("so_loc").length() > 0) { |
| 243 | + sql += F("Where Location = '"); |
| 244 | + sql += server.arg("so_loc"); |
| 245 | + sql += F("' "); |
| 246 | + } else |
| 247 | + sql += F("Where Location > '' "); |
| 248 | + sql += F("Group by Location "); |
| 249 | + if (server.arg("so_loc_count").length() > 0) { |
| 250 | + sql += F("Having Count(*) >= "); |
| 251 | + sql += server.arg("so_loc_count"); |
| 252 | + } |
| 253 | + } |
| 254 | + rc = sqlite3_prepare_v2(db1, sql.c_str(), 1000, &res, &tail); |
| 255 | + if (rc != SQLITE_OK) { |
| 256 | + String resp = F("Failed to fetch data: "); |
| 257 | + resp += sqlite3_errmsg(db1); |
| 258 | + resp += F("<br><br><a href='/'>back</a>"); |
| 259 | + server.send ( 200, "text/html", resp.c_str()); |
| 260 | + Serial.println(resp.c_str()); |
| 261 | + return; |
| 262 | + } |
| 263 | + |
| 264 | + server.setContentLength(CONTENT_LENGTH_UNKNOWN); |
| 265 | + String resp = F("<!DOCTYPE html><html><head>\ |
| 266 | + <title>StackOverflow Database query on ESP32 through web server</title>\ |
| 267 | + <style>\ |
| 268 | + body { font-family: Arial, Helvetica, Sans-Serif; font-size: large; Color: #000088; }\ |
| 269 | + </style><head><body><h1>Query StackOverflow Users db on Micro SD card attached to ESP32 through its web server</h1><h3>"); |
| 270 | + resp += sql; |
| 271 | + resp += F("</h3>"); |
| 272 | + if (rec_count >= 0) { |
| 273 | + resp += F("<p>No. of records: "); |
| 274 | + resp += rec_count; |
| 275 | + resp += F("</p>"); |
| 276 | + } |
| 277 | + resp += F("<table cellspacing='1' cellpadding='1' border='1'>"); |
| 278 | + server.send(200, "text/html", resp.c_str()); |
| 279 | + int cols = sqlite3_column_count(res); |
| 280 | + resp = F("<tr>"); |
| 281 | + for (int i = 0; i < cols; i++) { |
| 282 | + resp += F("<td>"); |
| 283 | + resp += (const char *) sqlite3_column_name(res, i); |
| 284 | + resp += F("</td>"); |
| 285 | + } |
| 286 | + resp += F("</tr>"); |
| 287 | + server.sendContent(resp); |
| 288 | + do { |
| 289 | + step_res = sqlite3_step(res); |
| 290 | + if (step_res == SQLITE_ROW) { |
| 291 | + resp = F("<tr>"); |
| 292 | + for (int i = 0; i < cols; i++) { |
| 293 | + resp += F("<td>"); |
| 294 | + resp += (const char *) sqlite3_column_text(res, i); |
| 295 | + resp += F("</td>"); |
| 296 | + } |
| 297 | + resp += F("</tr>"); |
| 298 | + server.sendContent(resp); |
| 299 | + rec_count++; |
| 300 | + } |
| 301 | + } while (step_res != SQLITE_DONE && step_res != SQLITE_ERROR); |
| 302 | + resp = F("</table>"); |
| 303 | + resp += F("<br>Time taken (seconds): "); |
| 304 | + resp += (micros()-start)/1000000; |
| 305 | + resp += F("<br><br><input type=button onclick='location.href=\"/\"' value='back'/>"); |
| 306 | + server.sendContent(resp); |
| 307 | + sqlite3_finalize(res); |
| 308 | + }); |
| 309 | + server.onNotFound(handleNotFound); |
| 310 | + server.begin(); |
| 311 | + Serial.println(F("HTTP server started")); |
| 312 | +} |
| 313 | + |
| 314 | +void loop ( void ) { |
| 315 | + server.handleClient(); |
| 316 | +} |
0 commit comments