diff --git a/CHANGES.txt b/CHANGES.txt index f362229..f04ecef 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,6 +3,26 @@ CHANGES This file contains a brief summary of changes made from previous versions of the connector. +1.2.0 - March 2020 +------------------ +* Added connect with default database. +* Added rows effected, last insert id. +* Stability and speed improvements. +* Removed infinite wait in read_packet() if connection dropped. + Merged wait_for_client() and wait_for_data() into one + universal wait_for_bytes(int). +* Improved connect() speed. +* Improved sending queries speed. + Sending the whole buffer at a time (not byte by byte). +* Fixed bug with check_ok_packet() return value. + check_ok_packet() simplified to get_packet_type(). +* Added checks for buffer validity in places it is going to be used. + Improved stability on "incorrect" methods calls. +* Restored WITH_SELECT, but defined by default. +* Added DEBUG define to avoid prints to Serial port. + Useful when Serial is used to communicate with sensors. +* Added example for ESP8266. + 1.1.1a - January 2016 --------------------- * Minor issue with deprecated #import fixed. No new functionality. diff --git a/README.md b/README.md index 86d8653..330930c 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,4 @@ This also means you can setup your own, local MySQL server to store your data fu The MySQL Connector/Arduino is a library that permits you to do exactly that and more! -See the reference manual in the extras folder for how to get starting using the library. - -Notice ------- -This is a new release of the Connector/Arduino library. Older, obsolete versions (version 1.0.4ga and prior) can be found on LaunchPad. - -If you have used the older versions and are finding this library thinking it is a new library, you're partially correct. This version is greatly improved over the old version. To see what changed, see the "Changes from Previous Versions" section in the reference manual in the extras folder. +See the all new [wiki documentation](https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki) for how to get starting using the library. diff --git a/examples/basic_insert/basic_insert.ino b/examples/basic_insert/basic_insert.ino index bce2ce0..abd165b 100644 --- a/examples/basic_insert/basic_insert.ino +++ b/examples/basic_insert/basic_insert.ino @@ -17,6 +17,9 @@ is an auto_increment, a string, and a timestamp. This will demonstrate how to save a date and time of when the row was inserted, which can help you determine when data was recorded or updated. + + For more information and documentation, visit the wiki: + https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki. INSTRUCTIONS FOR USE diff --git a/examples/basic_insert_esp8266/basic_insert_esp8266.ino b/examples/basic_insert_esp8266/basic_insert_esp8266.ino new file mode 100644 index 0000000..58470a4 --- /dev/null +++ b/examples/basic_insert_esp8266/basic_insert_esp8266.ino @@ -0,0 +1,81 @@ +/* + MySQL Connector/Arduino Example : connect by wifi + + This example demonstrates how to connect to a MySQL server from an + Arduino using an Arduino-compatible Wifi shield. Note that "compatible" + means it must conform to the Ethernet class library or be a derivative + with the same classes and methods. + + For more information and documentation, visit the wiki: + https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki. + + INSTRUCTIONS FOR USE + + 1) Change the address of the server to the IP address of the MySQL server + 2) Change the user and password to a valid MySQL user and password + 3) Change the SSID and pass to match your WiFi network + 4) Connect a USB cable to your Arduino + 5) Select the correct board and port + 6) Compile and upload the sketch to your Arduino + 7) Once uploaded, open Serial Monitor (use 115200 speed) and observe + + If you do not see messages indicating you have a connection, refer to the + manual for troubleshooting tips. The most common issues are the server is + not accessible from the network or the user name and password is incorrect. + + Created by: Dr. Charles A. Bell +*/ +#include // Use this for WiFi instead of Ethernet.h +#include +#include + +IPAddress server_addr(10,0,1,35); // IP of the MySQL *server* here +char user[] = "root"; // MySQL user login username +char password[] = "secret"; // MySQL user login password + +// Sample query +char INSERT_SQL[] = "INSERT INTO test_arduino.hello_arduino (message) VALUES ('Hello, Arduino!')"; + +// WiFi card example +char ssid[] = "your-ssid"; // your SSID +char pass[] = "ssid-password"; // your SSID Password + +WiFiClient client; // Use this for WiFi instead of EthernetClient +MySQL_Connection conn(&client); +MySQL_Cursor* cursor; + +void setup() +{ + Serial.begin(115200); + while (!Serial); // wait for serial port to connect. Needed for Leonardo only + + // Begin WiFi section + Serial.printf("\nConnecting to %s", ssid); + WiFi.begin(ssid, pass); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + // print out info about the connection: + Serial.println("\nConnected to network"); + Serial.print("My IP address is: "); + Serial.println(WiFi.localIP()); + + Serial.print("Connecting to SQL... "); + if (conn.connect(server_addr, 3306, user, password)) + Serial.println("OK."); + else + Serial.println("FAILED."); + + // create MySQL cursor object + cursor = new MySQL_Cursor(&conn); +} + +void loop() +{ + if (conn.connected()) + cursor->execute(INSERT_SQL); + + delay(5000); +} diff --git a/examples/basic_select/basic_select.ino b/examples/basic_select/basic_select.ino index 9cd9878..70cf2a4 100644 --- a/examples/basic_select/basic_select.ino +++ b/examples/basic_select/basic_select.ino @@ -15,6 +15,9 @@ CAUTION: Don't mix and match the examples. Use one or the other in your own sketch -- you'll get compilation errors at the least. + For more information and documentation, visit the wiki: + https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki. + INSTRUCTIONS FOR USE 1) Change the address of the server to the IP address of the MySQL server diff --git a/examples/complex_insert/complex_insert.ino b/examples/complex_insert/complex_insert.ino index ab150ad..460f70e 100644 --- a/examples/complex_insert/complex_insert.ino +++ b/examples/complex_insert/complex_insert.ino @@ -28,6 +28,9 @@ method so that it runs only once. Typically, you would have the INSERT in the loop() method after your code to read from the sensor. + For more information and documentation, visit the wiki: + https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki. + INSTRUCTIONS FOR USE 1) Create the database and table as shown above. diff --git a/examples/complex_select/complex_select.ino b/examples/complex_select/complex_select.ino index 40bccdd..0359dbc 100644 --- a/examples/complex_select/complex_select.ino +++ b/examples/complex_select/complex_select.ino @@ -9,6 +9,9 @@ the result set. Study this example until you are familiar with how to do this before writing your own sketch to read and consume query results. + For more information and documentation, visit the wiki: + https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki. + NOTICE: You must download and install the World sample database to run this sketch unaltered. See http://dev.mysql.com/doc/index-other.html. diff --git a/examples/connect/connect.ino b/examples/connect/connect.ino index 821f7b5..3414af1 100644 --- a/examples/connect/connect.ino +++ b/examples/connect/connect.ino @@ -4,8 +4,10 @@ This example demonstrates how to connect to a MySQL server from an Arduino using an Arduino-compatible Ethernet shield. Note that "compatible" means it must conform to the Ethernet class library or be a derivative - thereof. See the documentation located in the /docs folder for more - details. + with the same classes and methods. + + For more information and documentation, visit the wiki: + https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki. INSTRUCTIONS FOR USE diff --git a/examples/connect_by_hostname/connect_by_hostname.ino b/examples/connect_by_hostname/connect_by_hostname.ino index e9a5dd2..2024917 100644 --- a/examples/connect_by_hostname/connect_by_hostname.ino +++ b/examples/connect_by_hostname/connect_by_hostname.ino @@ -5,6 +5,9 @@ hostname for cases when you do not know the IP address of the server or it changes because it is in the cloud. + For more information and documentation, visit the wiki: + https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki. + INSTRUCTIONS FOR USE 1) Change the hostname variable to the hostname of the MySQL server diff --git a/examples/connect_default_database/connect_default_database.ino b/examples/connect_default_database/connect_default_database.ino new file mode 100644 index 0000000..8f46ce1 --- /dev/null +++ b/examples/connect_default_database/connect_default_database.ino @@ -0,0 +1,55 @@ +/* + MySQL Connector/Arduino Example : connect with default database + + This example demonstrates how to connect to a MySQL server and specifying + the default database when connecting. + + For more information and documentation, visit the wiki: + https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki. + + INSTRUCTIONS FOR USE + + 1) Change the address of the server to the IP address of the MySQL server + 2) Change the user and password to a valid MySQL user and password + 3) Connect a USB cable to your Arduino + 4) Select the correct board and port + 5) Compile and upload the sketch to your Arduino + 6) Once uploaded, open Serial Monitor (use 115200 speed) and observe + + If you do not see messages indicating you have a connection, refer to the + manual for troubleshooting tips. The most common issues are the server is + not accessible from the network or the user name and password is incorrect. + + Note: The MAC address can be anything so long as it is unique on your network. + + Created by: Dr. Charles A. Bell +*/ +#include +#include + +byte mac_addr[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; + +IPAddress server_addr(10,0,1,35); // IP of the MySQL *server* here +char user[] = "root"; // MySQL user login username +char password[] = "secret"; // MySQL user login password +char default_db = "test_arduino; + +EthernetClient client; +MySQL_Connection conn((Client *)&client); + +void setup() { + Serial.begin(115200); + while (!Serial); // wait for serial port to connect + Ethernet.begin(mac_addr); + Serial.println("Connecting..."); + if (conn.connect(server_addr, 3306, user, password, default_db)) { + delay(1000); + // You would add your code here to run a query once on startup. + } + else + Serial.println("Connection failed."); + conn.close(); +} + +void loop() { +} diff --git a/examples/connect_disconnect/connect_disconnect.ino b/examples/connect_disconnect/connect_disconnect.ino index fc8a9f6..98d9f78 100644 --- a/examples/connect_disconnect/connect_disconnect.ino +++ b/examples/connect_disconnect/connect_disconnect.ino @@ -8,6 +8,12 @@ control how long the connection is open and thus reduce the amount of time a connection is held open. It also helps for lossy connections. + This example demonstrates how to connect to a MySQL server and specifying + the default database when connecting. + + For more information and documentation, visit the wiki: + https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki. + INSTRUCTIONS FOR USE 1) Change the address of the server to the IP address of the MySQL server diff --git a/examples/connect_wifi/connect_wifi.ino b/examples/connect_wifi/connect_wifi.ino index 411f239..2ea5ccb 100644 --- a/examples/connect_wifi/connect_wifi.ino +++ b/examples/connect_wifi/connect_wifi.ino @@ -4,8 +4,10 @@ This example demonstrates how to connect to a MySQL server from an Arduino using an Arduino-compatible Wifi shield. Note that "compatible" means it must conform to the Ethernet class library or be a derivative - thereof. See the documentation located in the /docs folder for more - details. + with the same classes and methods. + + For more information and documentation, visit the wiki: + https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki. INSTRUCTIONS FOR USE diff --git a/examples/connect_wifi_101/connect_wifi_101.ino b/examples/connect_wifi_101/connect_wifi_101.ino index 26758ad..9065947 100644 --- a/examples/connect_wifi_101/connect_wifi_101.ino +++ b/examples/connect_wifi_101/connect_wifi_101.ino @@ -3,19 +3,9 @@ This example demonstrates how to connect to a MySQL server from an Arduino using using the new WiFi Shield 101 from arduino.cc. - - NOTICE NOTICE NOTICE - - The new WiFi 101 library is quite large. You should use this sketch and the - shield with the new Arduino Due or Zero. - - You should also use the latest Arduino IDE from arduino.cc. This sketch was - tested with release 1.6.7 from https://www.arduino.cc/en/Main/Software - running on a Due board. - - Also, make sure your hardware libraries are uptodate by visiting the - boards manager and installing updates for the boards you are tying to use - (e.g. Due, Zero). + + For more information and documentation, visit the wiki: + https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki. INSTRUCTIONS FOR USE diff --git a/examples/query_progmem/query_progmem.ino b/examples/query_progmem/query_progmem.ino index 2d49757..bec9399 100644 --- a/examples/query_progmem/query_progmem.ino +++ b/examples/query_progmem/query_progmem.ino @@ -6,6 +6,9 @@ method in the cursor class, const and PROGMEM to the string declaration and add the #include directive. + For more information and documentation, visit the wiki: + https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki. + NOTICE: You must download and install the World sample database to run this sketch unaltered. See http://dev.mysql.com/doc/index-other.html. diff --git a/examples/query_results/query_results.ino b/examples/query_results/query_results.ino index c5469e9..257daa9 100644 --- a/examples/query_results/query_results.ino +++ b/examples/query_results/query_results.ino @@ -4,6 +4,9 @@ This example demonstrates how to issue a SELECT query and how to read columns and rows from the result set. Study this example until you are familiar with how to do this before writing your own sketch to read and consume query results. + + For more information and documentation, visit the wiki: + https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki. NOTICE: You must download and install the World sample database to run this sketch unaltered. See http://dev.mysql.com/doc/index-other.html. diff --git a/examples/reboot/reboot.ino b/examples/reboot/reboot.ino index 4fbd91c..a7ef0b9 100644 --- a/examples/reboot/reboot.ino +++ b/examples/reboot/reboot.ino @@ -3,6 +3,9 @@ This example demonstrates how to reboot an Arduino if connection to the server is lost for a period of time. + + For more information and documentation, visit the wiki: + https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki. INSTRUCTIONS FOR USE diff --git a/extras/MySQL_Connector_Arduino_Reference_Manual.pdf b/extras/MySQL_Connector_Arduino_Reference_Manual.pdf deleted file mode 100644 index ee795a4..0000000 Binary files a/extras/MySQL_Connector_Arduino_Reference_Manual.pdf and /dev/null differ diff --git a/extras/Reference_Manual.txt b/extras/Reference_Manual.txt new file mode 100644 index 0000000..74eb2c0 --- /dev/null +++ b/extras/Reference_Manual.txt @@ -0,0 +1,4 @@ +The documentation for the connector has been moved to the wiki page on Github. For more information and documentation, visit the wiki: + +https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki. + diff --git a/keywords.txt b/keywords.txt index 4ab5120..08de09a 100755 --- a/keywords.txt +++ b/keywords.txt @@ -1,5 +1,5 @@ -connect KEYWORD1 -execute KEYWORD2 -show_results KEYWORD3 -connected KEYWORD4 -field_struct KEYWORD5 +connect KEYWORD2 +execute KEYWORD2 +show_results KEYWORD2 +connected KEYWORD2 +field_struct KEYWORD3 diff --git a/library.properties b/library.properties index b8b0523..549bb9d 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=MySQL Connector Arduino -version=1.1.1 -author=Dr. Charles Bell +version=1.2.0 +author=Dr. Charles Bell maintainer=Dr. Charles Bell sentence=Connects Arduino using Arduino Ethernet-compatible shields including the Ethernet Shield and WiFi Shield. paragraph=You can use this library to connect your Arduino project directly to a MySQL server without using an intermediate computer or a web- or cloud-based service. Having direct access to a database server means you can store data acquired from your project as well as check values stored in tables on the server. This also means you can setup your own, local MySQL server to store your data further removing the need for Internet connectivity. If that is not an issue, you can still connect to and store data on a MySQL server via your network, Internet, or even in the cloud! category=Communication -url=https://github.com/ChuckBell/MySQL_Connector_Arduino +url=https://github.com/ChuckBell/MySQL_Connector_Arduino/wiki architectures=* diff --git a/src/MySQL_Connection.cpp b/src/MySQL_Connection.cpp index 22b2151..7cbf83a 100644 --- a/src/MySQL_Connection.cpp +++ b/src/MySQL_Connection.cpp @@ -27,12 +27,16 @@ Version 1.0.4ga Updated by Dr. Charles A. Bell, July 2015. Version 1.1.0a Created by Dr. Charles A. Bell, January 2016. Version 1.1.1a Created by Dr. Charles A. Bell, January 2016. + Version 1.1.2b Created by Dr. Charles A. Bell, November 2016. + Version 1.2.0 Created by Dr. Charles A. Bell, March 2020. */ #include #include #include #define MAX_CONNECT_ATTEMPTS 3 +#define CONNECT_DELAY_MS 500 +#define SUCCESS 1 const char CONNECTED[] PROGMEM = "Connected to server version "; const char DISCONNECTED[] PROGMEM = "Disconnected."; @@ -50,37 +54,49 @@ const char DISCONNECTED[] PROGMEM = "Disconnected."; port[in] port number of the server user[in] user name password[in] (optional) user password + db[in] (optional) default database Returns boolean - True = connection succeeded */ boolean MySQL_Connection::connect(IPAddress server, int port, char *user, - char *password) + char *password, char *db) { int connected = 0; - int i = -1; + int retries = MAX_CONNECT_ATTEMPTS; - // Retry up to MAX_CONNECT_ATTEMPTS times 1 second apart. - do { - delay(1000); + // Retry up to MAX_CONNECT_ATTEMPTS times. + while (retries--) + { + Serial.println("...trying..."); connected = client->connect(server, port); - i++; - } while (i < MAX_CONNECT_ATTEMPTS && !connected); - - if (connected) { - read_packet(); - parse_handshake_packet(); - send_authentication_packet(user, password); - read_packet(); - if (check_ok_packet() != 0) { - parse_error_packet(); - return false; + if (connected != SUCCESS) { + Serial.print("...got: "); + Serial.print(connected); + Serial.println(" retrying..."); + delay(CONNECT_DELAY_MS); + } else { + break; } - show_error(CONNECTED); - Serial.println(server_version); - free(server_version); // don't need it anymore - return true; } - return false; + + if (connected != SUCCESS) + return false; + + read_packet(); + parse_handshake_packet(); + send_authentication_packet(user, password, db); + read_packet(); + if (get_packet_type() != MYSQL_OK_PACKET) { + parse_error_packet(); + return false; + } + + show_error(CONNECTED); + + Serial.println(server_version); + + free(server_version); // don't need it anymore + return true; } /* diff --git a/src/MySQL_Connection.h b/src/MySQL_Connection.h index 679be4c..6537b99 100644 --- a/src/MySQL_Connection.h +++ b/src/MySQL_Connection.h @@ -30,6 +30,8 @@ Version 1.0.4ga Updated by Dr. Charles A. Bell, July 2015. Version 1.1.0a Created by Dr. Charles A. Bell, January 2016. Version 1.1.1a Created by Dr. Charles A. Bell, January 2016. + Version 1.1.2b Created by Dr. Charles A. Bell, November 2016. + Version 1.2.0 Created by Dr. Charles A. Bell, March 2020. */ #ifndef MYSQL_CONNECTION_H #define MYSQL_CONNECTION_H @@ -40,7 +42,8 @@ class MySQL_Connection : public MySQL_Packet { public: MySQL_Connection(Client *client_instance) : MySQL_Packet(client_instance) {} - boolean connect(IPAddress server, int port, char *user, char *password); + boolean connect(IPAddress server, int port, char *user, char *password, + char *db=NULL); int connected() { return client->connected(); } const char *version() { return MYSQL_VERSION_STR; } void close(); diff --git a/src/MySQL_Cursor.cpp b/src/MySQL_Cursor.cpp index 774db08..500b257 100644 --- a/src/MySQL_Cursor.cpp +++ b/src/MySQL_Cursor.cpp @@ -26,6 +26,8 @@ Version 1.0.4ga Updated by Dr. Charles A. Bell, July 2015. Version 1.1.0a Created by Dr. Charles A. Bell, January 2016. Version 1.1.1a Created by Dr. Charles A. Bell, January 2016. + Version 1.1.2b Created by Dr. Charles A. Bell, November 2016. + Version 1.2.0 Created by Dr. Charles A. Bell, March 2020. */ #include @@ -44,15 +46,26 @@ const char NOT_CONNECTED[] PROGMEM = "ERROR: Class requires connected server."; */ MySQL_Cursor::MySQL_Cursor(MySQL_Connection *connection) { conn = connection; - if (!conn->connected()) { - Serial.println(NOT_CONNECTED); - } +#ifdef WITH_SELECT columns.num_fields = 0; for (int f = 0; f < MAX_FIELDS; f++) { columns.fields[f] = NULL; row.values[f] = NULL; } columns_read = false; + rows_affected = -1; + last_insert_id = -1; +#endif +} + + +/* + Destructor +*/ +MySQL_Cursor::~MySQL_Cursor() { +#ifdef WITH_SELECT + close(); +#endif } @@ -76,6 +89,11 @@ boolean MySQL_Cursor::execute(const char *query, boolean progmem) { int query_len; // length of query + if (!conn->connected()) { + conn->show_error(NOT_CONNECTED, true); + return false; + } + if (progmem) { query_len = (int)strlen_P(query); } else { @@ -99,6 +117,83 @@ boolean MySQL_Cursor::execute(const char *query, boolean progmem) } +/* + execute_query - execute a query + + This method sends the query string to the server and waits for a + response. If the result is a result set, it returns true, if it is + an error, it processes the error packet and prints the error via + Serial.print(). If it is an Ok packet, it parses the packet and + returns false. + + query_len[in] Number of bytes in the query string + + Returns boolean - true = result set available, + false = no result set returned. +*/ +boolean MySQL_Cursor::execute_query(int query_len) +{ + if (!conn->buffer) + return false; + + // Reset the rows affected and last insert id before query. + rows_affected = -1; + last_insert_id = -1; + + conn->store_int(&conn->buffer[0], query_len+1, 3); + conn->buffer[3] = byte(0x00); + conn->buffer[4] = byte(0x03); // command packet + + // Send the query + conn->client->write((uint8_t*)conn->buffer, query_len + 5); + conn->client->flush(); + + // Read a response packet and check it for Ok or Error. + conn->read_packet(); + int res = conn->get_packet_type(); + if (res == MYSQL_ERROR_PACKET) { + conn->parse_error_packet(); + return false; + } else if (res == MYSQL_OK_PACKET || res == MYSQL_EOF_PACKET) { + // Read the rows affected and last insert id. + int loc1 = conn->buffer[5]; // Location of rows affected + int loc2 = 5; + if (loc1 < 252) { + loc2++; + } else if (loc1 == 252) { + loc2 += 2; + } else if (loc1 == 253) { + loc2 += 3; + } else { + loc2 += 8; + } + rows_affected = conn->read_lcb_int(5); + if (rows_affected > 0) { + last_insert_id = conn->read_lcb_int(loc2); + } + return true; + } + + // Not an Ok packet, so we now have the result set to process. +#ifdef WITH_SELECT + columns_read = false; +#endif + return true; +} + + +#ifdef WITH_SELECT +/* + Close + + Takes care of removing allocated memory. +*/ +void MySQL_Cursor::close() { + free_columns_buffer(); + free_row_buffer(); +} + + /* get_columns - Get a list of the columns (fields) @@ -197,58 +292,6 @@ void MySQL_Cursor::show_results() { } -/* - Close - - Takes care of removing allocated memory. -*/ -void MySQL_Cursor::close() { - free_columns_buffer(); - free_row_buffer(); -} - - -// Begin private methods - -/* - execute_query - execute a query - - This method sends the query string to the server and waits for a - response. If the result is a result set, it returns true, if it is - an error, it processes the error packet and prints the error via - Serial.print(). If it is an Ok packet, it parses the packet and - returns false. - - query_len[in] Number of bytes in the query string - - Returns boolean - true = result set available, - false = no result set returned. -*/ -boolean MySQL_Cursor::execute_query(int query_len) -{ - conn->store_int(&conn->buffer[0], query_len+1, 3); - conn->buffer[3] = byte(0x00); - conn->buffer[4] = byte(0x03); // command packet - - // Send the query - for (int c = 0; c < query_len+5; c++) - conn->client->write(conn->buffer[c]); - - // Read a response packet and check it for Ok or Error. - conn->read_packet(); - int res = conn->check_ok_packet(); - if (res == MYSQL_ERROR_PACKET) { - conn->parse_error_packet(); - return false; - } else if (!res) { - return false; - } - // Not an Ok packet, so we now have the result set to process. - columns_read = false; - return true; -} - - /* clear_ok_packet - clear last Ok packet (if present) @@ -265,12 +308,14 @@ bool MySQL_Cursor::clear_ok_packet() { num = conn->client->available(); if (num > 0) { conn->read_packet(); - if (conn->check_ok_packet() != 0) { + if (conn->get_packet_type() != MYSQL_OK_PACKET) { conn->parse_error_packet(); return false; } } } while (num > 0); + rows_affected = -1; + last_insert_id = -1; return true; } @@ -335,12 +380,21 @@ void MySQL_Cursor::free_row_buffer() { Returns string - String from the buffer */ char *MySQL_Cursor::read_string(int *offset) { + char *str; int len_bytes = conn->get_lcb_len(conn->buffer[*offset]); int len = conn->read_int(*offset, len_bytes); - char *str = (char *)malloc(len+1); - strncpy(str, (char *)&conn->buffer[*offset+len_bytes], len); - str[len] = 0x00; - *offset += len_bytes+len; + if (len == 251) { + // This is a null field. + str = (char *)malloc(5); + strncpy(str, "NULL", 4); + str[4] = 0x00; + *offset += len_bytes; + } else { + str = (char *)malloc(len+1); + strncpy(str, (char *)&conn->buffer[*offset+len_bytes], len); + str[len] = 0x00; + *offset += len_bytes+len; + } return str; } @@ -377,7 +431,7 @@ int MySQL_Cursor::get_field(field_struct *fs) { // Read field packets until EOF conn->read_packet(); - if (conn->buffer[4] != MYSQL_EOF_PACKET) { + if (conn->buffer && conn->buffer[4] != MYSQL_EOF_PACKET) { // calculate location of db len_bytes = conn->get_lcb_len(4); len = conn->read_int(4, len_bytes); @@ -416,7 +470,7 @@ int MySQL_Cursor::get_field(field_struct *fs) { int MySQL_Cursor::get_row() { // Read row packets conn->read_packet(); - if (conn->buffer[4] != MYSQL_EOF_PACKET) + if (conn->buffer && conn->buffer[4] != MYSQL_EOF_PACKET) return 0; return MYSQL_EOF_PACKET; } @@ -484,3 +538,5 @@ int MySQL_Cursor::get_row_values() { } return res; } + +#endif // WITH_SELECT diff --git a/src/MySQL_Cursor.h b/src/MySQL_Cursor.h index 9e83730..5447da3 100644 --- a/src/MySQL_Cursor.h +++ b/src/MySQL_Cursor.h @@ -30,14 +30,19 @@ Version 1.0.4ga Updated by Dr. Charles A. Bell, July 2015. Version 1.1.0a Created by Dr. Charles A. Bell, January 2016. Version 1.1.1a Created by Dr. Charles A. Bell, January 2016. + Version 1.1.2b Created by Dr. Charles A. Bell, November 2016. + Version 1.2.0 Created by Dr. Charles A. Bell, March 2020. */ #ifndef MYSQL_QUERY_H #define MYSQL_QUERY_H #include +#define WITH_SELECT // Comment this if you don't need SELECT queries. + // Reduces memory footprint of the library. #define MAX_FIELDS 0x20 // Maximum number of fields. Reduce to save memory. Default=32 +#ifdef WITH_SELECT // Structure for retrieving a field (minimal implementation). typedef struct { char *db; @@ -55,34 +60,47 @@ typedef struct { typedef struct { char *values[MAX_FIELDS]; } row_values; +#endif // WITH_SELECT class MySQL_Cursor { public: MySQL_Cursor(MySQL_Connection *connection); - ~MySQL_Cursor() { close(); }; + ~MySQL_Cursor(); boolean execute(const char *query, boolean progmem=false); + + private: + boolean execute_query(int query_len); + +#ifdef WITH_SELECT + public: + void close(); column_names *get_columns(); row_values *get_next_row(); void show_results(); - void close(); + int get_rows_affected() { return rows_affected; } + int get_last_insert_id() { return last_insert_id; } private: - boolean columns_read; - MySQL_Connection *conn; - int num_cols; - column_names columns; - row_values row; - void free_columns_buffer(); void free_row_buffer(); bool clear_ok_packet(); - boolean execute_query(int query_len); + char *read_string(int *offset); int get_field(field_struct *fs); int get_row(); boolean get_fields(); int get_row_values(); column_names *query_result(); + + boolean columns_read; + int num_cols; + column_names columns; + row_values row; + int rows_affected; + int last_insert_id; +#endif + + MySQL_Connection *conn; }; #endif diff --git a/src/MySQL_Encrypt_Sha1.cpp b/src/MySQL_Encrypt_Sha1.cpp index 16160ca..7b4d227 100644 --- a/src/MySQL_Encrypt_Sha1.cpp +++ b/src/MySQL_Encrypt_Sha1.cpp @@ -86,12 +86,14 @@ void Encrypt_SHA1::addUncounted(uint8_t data) { size_t Encrypt_SHA1::write(uint8_t data) { ++byteCount; addUncounted(data); + return 1; } size_t Encrypt_SHA1::write(uint8_t* data, int length) { for (int i=0; i #include #include #define MYSQL_DATA_TIMEOUT 3000 // Wifi client wait in milliseconds -#define MYSQL_WAIT_INTERVAL 100 // WiFi client wait interval +#define MYSQL_WAIT_INTERVAL 300 // WiFi client wait interval /* Constructor @@ -53,6 +55,7 @@ MySQL_Packet::MySQL_Packet(Client *client_instance) { EOL[in] True if we print EOLN character */ void MySQL_Packet::show_error(const char *msg, bool EOL) { +#ifdef DEBUG char pos; while ((pos = pgm_read_byte(msg))) { Serial.print(pos); @@ -60,6 +63,7 @@ void MySQL_Packet::show_error(const char *msg, bool EOL) { } if (EOL) Serial.println(); +#endif } /* @@ -84,12 +88,14 @@ void MySQL_Packet::show_error(const char *msg, bool EOL) { 23 (filler) always 0x00... n (Null-Terminated String) user n (Length Coded Binary) scramble_buff (1 + x bytes) - n (Null-Terminated String) databasename (optional + n (Null-Terminated String) databasename (optional) user[in] User name password[in] password + db[in] default database */ -void MySQL_Packet::send_authentication_packet(char *user, char *password) +void MySQL_Packet::send_authentication_packet(char *user, char *password, + char *db) { if (buffer != NULL) free(buffer); @@ -99,7 +105,7 @@ void MySQL_Packet::send_authentication_packet(char *user, char *password) int size_send = 4; // client flags - buffer[size_send] = byte(0x85); + buffer[size_send] = byte(0x0D); buffer[size_send+1] = byte(0xa6); buffer[size_send+2] = byte(0x03); buffer[size_send+3] = byte(0x00); @@ -136,13 +142,14 @@ void MySQL_Packet::send_authentication_packet(char *user, char *password) } free(scramble); - // terminate password response - buffer[size_send] = 0x00; - size_send += 1; - - // database - buffer[size_send+1] = 0x00; - size_send += 1; + if (db) { + memcpy((char *)&buffer[size_send], db, strlen(db)); + size_send += strlen(db) + 1; + buffer[size_send-1] = 0x00; + } else { + buffer[size_send+1] = 0x00; + size_send += 1; + } // Write packet size int p_size = size_send - 4; @@ -150,8 +157,8 @@ void MySQL_Packet::send_authentication_packet(char *user, char *password) buffer[3] = byte(0x01); // Write the packet - for (int i = 0; i < size_send; i++) - client->write(buffer[i]); + client->write((uint8_t*)buffer, size_send); + client->flush(); } @@ -206,61 +213,41 @@ boolean MySQL_Packet::scramble_password(char *password, byte *pwd_hash) { } /* - wait_for_client - Wait until data is available for reading + wait_for_bytes - Wait until data is available for reading This method is used to permit the connector to respond to servers that have high latency or execute long queries. The timeout is - set by MYSQL_MAX_TIMEOUT. Adjust this value to match the performance of + set by MYSQL_DATA_TIMEOUT. Adjust this value to match the performance of your server and network. It is also used to read how many bytes in total are available from the server. Thus, it can be used to know how large a data burst is from the server. + bytes_need[in] Bytes count to wait for + Returns integer - Number of bytes available to read. */ -int MySQL_Packet::wait_for_client() { +int MySQL_Packet::wait_for_bytes(int bytes_need) +{ + const long wait_till = millis() + MYSQL_DATA_TIMEOUT; int num = 0; - int timeout = 0; - do { - num = client->available(); - timeout++; - if (num < MYSQL_MIN_BYTES and timeout < MYSQL_MAX_TIMEOUT) { - delay(100); // adjust for network latency - } - } while (num < MYSQL_MIN_BYTES and timeout < MYSQL_MAX_TIMEOUT); - return num; -} + long now = 0; -/* - wait_for_data - - This method waits until client is sending data. It will timeout if no - data is available for more than MYSQL_DATA_TIMEOUT in milliseconds in WAIT_INTERVAL - milliseconds intervals. + do + { + now = millis(); + num = client->available(); + if (num < bytes_need) + delay(MYSQL_WAIT_INTERVAL); + else + break; + } while (now < wait_till); - Returns boolean - true = no timeout, data available; false = timeout, no data. -*/ -boolean MySQL_Packet::wait_for_data() { - bool data_available = client->available(); - int milliseconds = 0; + if (num == 0 && now >= wait_till) + client->stop(); - // Check to see if data is available right away, don't wait. - if (data_available) { - return true; - } - // No data, so we wait. - while ((!data_available) && (milliseconds < MYSQL_DATA_TIMEOUT)) { - data_available = client->available(); - if (data_available) { - return true; - } else { - delay(MYSQL_WAIT_INTERVAL); - milliseconds += MYSQL_WAIT_INTERVAL; - } - } - show_error(READ_TIMEOUT, true); - return false; + return num; } /* @@ -281,32 +268,31 @@ boolean MySQL_Packet::wait_for_data() { void MySQL_Packet::read_packet() { byte local[4]; - if (buffer != NULL) + if (buffer) { free(buffer); - - int avail_bytes = wait_for_client(); - while (avail_bytes < 4) { - avail_bytes = wait_for_client(); + buffer = NULL; } // Read packet header - for (int i = 0; i < 4; i++) { - // Wait for client. Abort if no data after TIMEOUT_DATA milliseconds - if (!wait_for_data()) { - return; - } - local[i] = client->read(); + if (wait_for_bytes(4) < 4) { + show_error(READ_TIMEOUT, true); + return; } + for (int i = 0; i < 4; i++) + local[i] = client->read(); // Get packet length packet_len = local[0]; packet_len += (local[1] << 8); packet_len += ((uint32_t)local[2] << 16); + // We must wait for slow arriving packets for Ethernet shields only. - avail_bytes = wait_for_client(); - while (avail_bytes < packet_len) { - avail_bytes = wait_for_client(); +/* + if (wait_for_bytes(packet_len) < packet_len) { + show_error(READ_TIMEOUT, true); + return; } +*/ // Check for valid packet. if (packet_len < 0) { show_error(PACKET_ERROR, true); @@ -320,13 +306,8 @@ void MySQL_Packet::read_packet() { for (int i = 0; i < 4; i++) buffer[i] = local[i]; - for (int i = 4; i < packet_len+4; i++) { - // Wait for client. Abort if no data after TIMEOUT_DATA milliseconds - if (!wait_for_data()) { - return; - } + for (int i = 4; i < packet_len+4; i++) buffer[i] = client->read(); - } } @@ -355,6 +336,9 @@ void MySQL_Packet::read_packet() { a scramble seed */ void MySQL_Packet::parse_handshake_packet() { + if (!buffer) + return; + int i = 5; do { i++; @@ -395,20 +379,23 @@ void MySQL_Packet::parse_handshake_packet() { n message */ void MySQL_Packet::parse_error_packet() { +#ifdef DEBUG Serial.print("Error: "); Serial.print(read_int(5, 2)); Serial.print(" = "); + + if (!buffer) + return; + for (int i = 0; i < packet_len-9; i++) Serial.print((char)buffer[i+13]); Serial.println("."); +#endif } /* - check_ok_packet - Decipher an Ok packet from the server. - - This method attempts to parse an Ok packet. If the packet is not an - Ok, packet, it returns the packet type. + get_packet_type - Returns the packet type received from the server. Bytes Name ----- ---- @@ -421,11 +408,12 @@ void MySQL_Packet::parse_error_packet() { Returns integer - 0 = successful parse, packet type if not an Ok packet */ -int MySQL_Packet::check_ok_packet() { +int MySQL_Packet::get_packet_type() { + if (!buffer) + return -1; + int type = buffer[4]; - if (type != MYSQL_OK_PACKET) - return type; - return 0; + return type; } @@ -440,6 +428,9 @@ int MySQL_Packet::check_ok_packet() { Returns integer - number of bytes integer consumes */ int MySQL_Packet::get_lcb_len(int offset) { + if (!buffer) + return 0; + int read_len = buffer[offset]; if (read_len > 250) { // read type: @@ -450,8 +441,10 @@ int MySQL_Packet::get_lcb_len(int offset) { read_len = 3; else if (type == 0xfe) read_len = 8; + } else { + read_len = 1; } - return 1; + return read_len; } /* @@ -468,6 +461,8 @@ int MySQL_Packet::get_lcb_len(int offset) { int MySQL_Packet::read_int(int offset, int size) { int value = 0; int new_size = 0; + if (!buffer) + return -1; if (size == 0) new_size = get_lcb_len(offset); if (size == 1) @@ -475,7 +470,7 @@ int MySQL_Packet::read_int(int offset, int size) { new_size = size; int shifter = (new_size - 1) * 8; for (int i = new_size; i > 0; i--) { - value += (byte)(buffer[i-1] << shifter); + value += (buffer[i-1] << shifter); shifter -= 8; } return value; @@ -513,6 +508,40 @@ void MySQL_Packet::store_int(byte *buff, long value, int size) { } } +/* + read_lcb_int - Read an integer with len encoded byte + + This reads an integer from the buffer looking at the first byte in the offset + as the encoded length of the integer. + + offset[in] offset from start of buffer + + Returns integer - integer from the buffer +*/ +int MySQL_Packet::read_lcb_int(int offset) { + int len_size = 0; + int size = 0; + int value = 0; + if (!buffer) + return -1; + len_size = buffer[offset]; + if (len_size < 252) { + return buffer[offset]; + } else if (len_size == 252) { + len_size = 2; + } else if (len_size == 253) { + len_size = 3; + } else { + len_size = 8; + } + int shifter = (len_size-1) * 8; + for (int i = len_size; i > 0; i--) { + value += (buffer[offset+i] << shifter); + shifter -= 8; + } + return value; +} + /* print_packet - Print the contents of a packet via Serial.print @@ -522,6 +551,9 @@ void MySQL_Packet::store_int(byte *buff, long value, int size) { delete this method. */ void MySQL_Packet::print_packet() { + if (!buffer) + return; + Serial.print("Packet: "); Serial.print(buffer[3]); Serial.print(" contains "); diff --git a/src/MySQL_Packet.h b/src/MySQL_Packet.h index 64cb320..20399f1 100644 --- a/src/MySQL_Packet.h +++ b/src/MySQL_Packet.h @@ -29,18 +29,26 @@ Version 1.0.4ga Updated by Dr. Charles A. Bell, July 2015. Version 1.1.0a Created by Dr. Charles A. Bell, January 2016. Version 1.1.1a Created by Dr. Charles A. Bell, January 2016. + Version 1.1.2b Created by Dr. Charles A. Bell, November 2016. + Version 1.2.0 Created by Dr. Charles A. Bell, March 2020. */ #ifndef MYSQL_PACKET_H #define MYSQL_PACKET_H -#include +#ifdef ARDUINO_ARCH_ESP32 + #include +#elif ARDUINO_ARCH_ESP8266 + #include +#else +// #include + #include +#endif -#define MYSQL_MAX_TIMEOUT 10 -#define MYSQL_MIN_BYTES 8 #define MYSQL_OK_PACKET 0x00 #define MYSQL_EOF_PACKET 0xfe #define MYSQL_ERROR_PACKET 0xff -#define MYSQL_VERSION_STR "1.1.1a" +#define MYSQL_VERSION_STR "1.2.0" +#define DEBUG const char MEMORY_ERROR[] PROGMEM = "Memory error."; const char PACKET_ERROR[] PROGMEM = "Packet error."; @@ -55,17 +63,18 @@ class MySQL_Packet { MySQL_Packet(Client *client_instance); boolean complete_handshake(char *user, char *password); - void send_authentication_packet(char *user, char *password); + void send_authentication_packet(char *user, char *password, + char *db=NULL); void parse_handshake_packet(); boolean scramble_password(char *password, byte *pwd_hash); void read_packet(); - int check_ok_packet(); + int get_packet_type(); void parse_error_packet(); int get_lcb_len(int offset); int read_int(int offset, int size=0); void store_int(byte *buff, long value, int size); - int wait_for_client(); - boolean wait_for_data(); + int read_lcb_int(int offset); + int wait_for_bytes(int bytes_count); void show_error(const char *msg, bool EOL = false); void print_packet();