From 54964aa0279f8d2123a446f383d3d1137bedc6a8 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 21 Aug 2023 12:52:41 +0700 Subject: [PATCH] Fix TinyGSMClient integration and examples. --- examples/GSM/GSM.ino | 57 ++-- library.json | 2 +- library.properties | 2 +- src/ESP_Google_Sheet_Client.cpp | 9 +- src/ESP_Google_Sheet_Client.h | 7 +- src/auth/GAuthManager.cpp | 65 +++- src/auth/GAuthManager.h | 11 +- src/client/GS_TCP_Client.h | 91 ++--- src/client/WiFiClientImpl.h | 565 ++++++++++++++++++++++++++++++++ 9 files changed, 711 insertions(+), 98 deletions(-) create mode 100644 src/client/WiFiClientImpl.h diff --git a/examples/GSM/GSM.ino b/examples/GSM/GSM.ino index c96313b..ffb31b5 100644 --- a/examples/GSM/GSM.ino +++ b/examples/GSM/GSM.ino @@ -13,7 +13,8 @@ // This example shows how to use TTGO T-A7670 (ESP32 with SIMCom SIMA7670) and TinyGSMClient This example to connect to Google API. -// To allow TinyGSM library integration, the following macro should be defined in src/ESP_Google_Sheet_Client_FS_Config.h. +// To allow TinyGSM library integration, the following macro should be defined in src/ESP_Google_Sheet_Client_FS_Config.h or +// your custom config file src/Custom_ESP_GOOGLE_SHEET_CLIENT_FS_Config // #define TINY_GSM_MODEM_SIM7600 #define TINY_GSM_MODEM_SIM7600 // SIMA7670 Compatible with SIM7600 AT instructions @@ -42,6 +43,9 @@ const char apn[] = "YourAPN"; const char gprsUser[] = ""; const char gprsPass[] = ""; +#define uS_TO_S_FACTOR 1000000ULL // Conversion factor for micro seconds to seconds +#define TIME_TO_SLEEP 600 // Time ESP32 will go to sleep (in seconds) + #define UART_BAUD 115200 #define PIN_DTR 25 #define PIN_TX 26 @@ -59,7 +63,6 @@ const char gprsPass[] = ""; #define SD_CS 13 #include - #include // For how to create Service Account and how to use the library, go to https://github.com/mobizt/ESP-Google-Sheet-Client @@ -72,12 +75,6 @@ const char gprsPass[] = ""; // Service Account's private key const char PRIVATE_KEY[] PROGMEM = "-----BEGIN PRIVATE KEY-----XXXXXXXXXXXX-----END PRIVATE KEY-----\n"; -// Set serial for debug console -#define SerialMon Serial - -// Set serial for AT commands (to the module) -#define SerialAT Serial1 - TinyGsm modem(SerialAT); TinyGsmClient gsm_client(modem); @@ -90,14 +87,35 @@ void setupGsheet(); void tokenStatusCallback(TokenInfo info); -void initModem() +void setup() { - if (modem.isGprsConnected()) - { - modem.gprsDisconnect(); - SerialMon.println(F("GPRS disconnected")); - } + SerialMon.begin(115200); + + delay(10); + pinMode(BAT_EN, OUTPUT); + digitalWrite(BAT_EN, HIGH); + + // A7670 Reset + pinMode(RESET, OUTPUT); + digitalWrite(RESET, LOW); + delay(100); + digitalWrite(RESET, HIGH); + delay(3000); + digitalWrite(RESET, LOW); + + pinMode(PWR_PIN, OUTPUT); + digitalWrite(PWR_PIN, LOW); + delay(100); + digitalWrite(PWR_PIN, HIGH); + delay(1000); + digitalWrite(PWR_PIN, LOW); + + DBG("Wait..."); + + delay(3000); + + SerialAT.begin(UART_BAUD, SERIAL_8N1, PIN_RX, PIN_TX); // Restart takes quite some time // To skip it, call init() instead of restart() @@ -118,16 +136,7 @@ void initModem() if (modem.waitResponse(10000L) != 1) { DBG(" setNetworkMode faill"); - return; } -} - -void setup() -{ - - SerialMon.begin(115200); - - SerialAT.begin(UART_BAUD, SERIAL_8N1, PIN_RX, PIN_TX); String name = modem.getModemName(); DBG("Modem Name:", name); @@ -135,8 +144,6 @@ void setup() String modemInfo = modem.getModemInfo(); DBG("Modem Info:", modemInfo); - initModem(); - GSheet.printf("ESP Google Sheet Client v%s\n\n", ESP_GOOGLE_SHEET_CLIENT_VERSION); GSheet.setGSMClient(&gsm_client, &modem, GSM_PIN, apn, gprsUser, gprsPass); diff --git a/library.json b/library.json index 60f43f0..58f0669 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "ESP-Google-Sheet-Client", - "version": "1.4.0", + "version": "1.4.1", "keywords": "communication, REST, esp32, esp8266, raspberrypi, arduino", "description": "Arduino Google Sheet REST client library for Arduino devices. This library allows devices to communicate with Google Sheet API to read, edit and delete the spreadsheets", "repository": { diff --git a/library.properties b/library.properties index 76f734d..9e4e33c 100644 --- a/library.properties +++ b/library.properties @@ -1,6 +1,6 @@ name=ESP-Google-Sheet-Client -version=1.4.0 +version=1.4.1 author=Mobizt diff --git a/src/ESP_Google_Sheet_Client.cpp b/src/ESP_Google_Sheet_Client.cpp index 13c5c68..2bf76d9 100644 --- a/src/ESP_Google_Sheet_Client.cpp +++ b/src/ESP_Google_Sheet_Client.cpp @@ -1,9 +1,9 @@ /** - * Google Sheet Client, ESP_GOOGLE_SHEET_CLIENT_Google_Sheet_Client.cpp v1.4.0 + * Google Sheet Client, ESP_GOOGLE_SHEET_CLIENT_Google_Sheet_Client.cpp v1.4.1 * * This library supports Espressif ESP8266 and ESP32 MCUs * - * Created August 13, 2023 + * Created August 21, 2023 * * The MIT License (MIT) * Copyright (c) 2022 K. Suwatchai (Mobizt) @@ -192,9 +192,7 @@ void GSheetClass::setCert(const char *ca) { cert_updated = true; cert_addr = addr; -#if defined(ESP8266) || defined(MB_ARDUINO_PICO) waitClockReady(); -#endif } } @@ -206,10 +204,7 @@ void GSheetClass::setCertFile(const char *filename, esp_google_sheet_file_storag if (config.cert.file.length() > 0) { cert_updated = true; - -#if defined(ESP8266) || defined(MB_ARDUINO_PICO) waitClockReady(); -#endif } } diff --git a/src/ESP_Google_Sheet_Client.h b/src/ESP_Google_Sheet_Client.h index c3c97a5..3208b74 100644 --- a/src/ESP_Google_Sheet_Client.h +++ b/src/ESP_Google_Sheet_Client.h @@ -1,13 +1,13 @@ #ifndef ESP_GOOGLE_SHEET_CLIENT_VERSION -#define ESP_GOOGLE_SHEET_CLIENT_VERSION "1.4.0" +#define ESP_GOOGLE_SHEET_CLIENT_VERSION "1.4.1" #endif /** - * Google Sheet Client, ESP_Google_Sheet_Client.h v1.3.6 + * Google Sheet Client, ESP_Google_Sheet_Client.h v1.4.1 * * This library supports Espressif ESP8266 and ESP32 MCUs * - * Created August 13, 2023 + * Created August 21, 2023 * * The MIT License (MIT) * Copyright (c) 2023 K. Suwatchai (Mobizt) @@ -113,6 +113,7 @@ class GSheetClass MB_String mGetValue(MB_String &response, const char *key); bool createPermission(MB_String &response, const char *fileId, const char *role, const char *type, const char *email); bool setClock(float gmtOffset); + bool getTime(float gmtOffset); void setClient(Client *client, ESP_GOOGLE_SHEET_CLIENT_NetworkConnectionRequestCallback networkConnectionCB, ESP_GOOGLE_SHEET_CLIENT_NetworkStatusRequestCallback networkStatusCB); void setGSMClient(Client *client, void *modem = nullptr, const char *pin = nullptr, const char *apn = nullptr, const char *user = nullptr, const char *password = nullptr); diff --git a/src/auth/GAuthManager.cpp b/src/auth/GAuthManager.cpp index 7a27e16..3ff8338 100644 --- a/src/auth/GAuthManager.cpp +++ b/src/auth/GAuthManager.cpp @@ -1,9 +1,9 @@ /** - * Google Sheet Client, GAuthManager v1.0.3 + * Google Sheet Client, GAuthManager v1.0.4 * * This library supports Espressif ESP8266, ESP32 and Raspberry Pi Pico MCUs. * - * Created August 13, 2023 + * Created August 21, 2023 * * The MIT License (MIT) * Copyright (c) 2022 K. Suwatchai (Mobizt) @@ -70,9 +70,15 @@ void GAuthManager::newClient(GS_TCP_Client **client) if (!*client) { *client = new GS_TCP_Client(); - // restore only external client (gsm client integration cannot restore) + if (_cli_type == esp_google_sheet_client_type_external_basic_client) (*client)->setClient(_cli, _net_con_cb, _net_stat_cb); + else if (_cli_type == esp_google_sheet_client_type_external_gsm_client) + { +#if defined(ESP_GOOGLE_SHEET_CLIENT_GSM_MODEM_IS_AVAILABLE) + (*client)->setGSMClient(_cli, _modem, _pin.c_str(), _apn.c_str(), _user.c_str(), _password.c_str()); +#endif + } else (*client)->_client_type = _cli_type; } @@ -82,11 +88,23 @@ void GAuthManager::freeClient(GS_TCP_Client **client) { if (*client) { - // Keep external client pointers _cli_type = (*client)->type(); - _net_con_cb = (*client)->_network_connection_cb; - _net_stat_cb = (*client)->_network_status_cb; _cli = (*client)->_basic_client; + if (_cli_type == esp_google_sheet_client_type_external_basic_client) + { + _net_con_cb = (*client)->_network_connection_cb; + _net_stat_cb = (*client)->_network_status_cb; + } + else if (_cli_type == esp_google_sheet_client_type_external_gsm_client) + { +#if defined(ESP_GOOGLE_SHEET_CLIENT_GSM_MODEM_IS_AVAILABLE) + _pin = (*client)->_pin; + _apn = (*client)->_apn; + _user = (*client)->_user; + _password = (*client)->_password; + _modem = (*client)->_modem; +#endif + } delete *client; } *client = nullptr; @@ -403,6 +421,27 @@ void GAuthManager::freeJson() resultPtr = nullptr; } +void GAuthManager::tryGetTime() +{ + if (!tcpClient || config->internal.clock_rdy) + return; + + _cli_type = tcpClient->type(); + + if (tcpClient->type() == esp_google_sheet_client_type_external_gsm_client) + { + uint32_t _time = tcpClient->gprsGetTime(); + if (_time > 0) + { + *mb_ts = _time; + TimeHelper::setTimestamp(_time, mb_ts_offset); + config->internal.clock_rdy = TimeHelper::clockReady(mb_ts, mb_ts_offset); + } + } + else + TimeHelper::syncClock(mb_ts, mb_ts_offset, config->time_zone, config); +} + void GAuthManager::tokenProcessingTask() { // We don't have to use memory reserved tasks e.g., RTOS task in ESP32 for this JWT @@ -445,17 +484,7 @@ void GAuthManager::tokenProcessingTask() } // check or set time again - if (_cli_type == esp_google_sheet_client_type_external_gsm_client) - { - uint32_t _time = tcpClient->gprsGetTime(); - if (_time > 0) - { - *mb_ts = _time; - TimeHelper::setTimestamp(_time, mb_ts_offset); - } - } - else - TimeHelper::syncClock(mb_ts, mb_ts_offset, config->time_zone, config); + tryGetTime(); // exit task immediately if time is not ready synched // which handleToken function should run repeatedly to enter this function again. @@ -473,7 +502,7 @@ void GAuthManager::tokenProcessingTask() { // time must be set first - TimeHelper::syncClock(mb_ts, mb_ts_offset, config->time_zone, config); + tryGetTime(); config->internal.last_jwt_begin_step_millis = millis(); if (config->internal.clock_rdy) diff --git a/src/auth/GAuthManager.h b/src/auth/GAuthManager.h index 4cc6fe1..1526133 100644 --- a/src/auth/GAuthManager.h +++ b/src/auth/GAuthManager.h @@ -1,9 +1,9 @@ /** - * Google Sheet Client, GAuthManager v1.0.3 + * Google Sheet Client, GAuthManager v1.0.4 * * This library supports Espressif ESP8266, ESP32 and Raspberry Pi Pico MCUs. * - * Created August 13, 2023 + * Created August 21, 2023 * * The MIT License (MIT) * Copyright (c) 2022 K. Suwatchai (Mobizt) @@ -78,6 +78,11 @@ class GAuthManager ESP_GOOGLE_SHEET_CLIENT_NetworkStatusRequestCallback _net_stat_cb = NULL; Client *_cli = nullptr; +#if defined(ESP_GOOGLE_SHEET_CLIENT_GSM_MODEM_IS_AVAILABLE) + MB_String _pin, _apn, _user, _password; + void *_modem = nullptr; +#endif + /* intitialize the class */ void begin(esp_google_sheet_auth_cfg_t *cfg, MB_FS *mbfs, uint32_t *mb_ts, uint32_t *mb_ts_offset); void end(); @@ -117,6 +122,8 @@ class GAuthManager bool handleTaskError(int code, int httpCode = 0); // parse the auth token response bool handleResponse(GS_TCP_Client *client, int &httpCode, MB_String &payload, bool stopSession = true); + /* Get time */ + void tryGetTime(); /* process the tokens (generation, signing, request and refresh) */ void tokenProcessingTask(); /* encode and sign the JWT token */ diff --git a/src/client/GS_TCP_Client.h b/src/client/GS_TCP_Client.h index 7dbbca0..6b8250a 100644 --- a/src/client/GS_TCP_Client.h +++ b/src/client/GS_TCP_Client.h @@ -36,6 +36,16 @@ #if defined(ESP32) #include "IPAddress.h" #include "lwip/sockets.h" + +#if defined(ESP_GOOGLE_SHEET_CLIENT_WIFI_IS_AVAILABLE) +#define WIFI_HAS_HOST_BY_NAME +#endif +#include "../client/WiFiClientImpl.h" +#define BASE_WIFICLIENT WiFiClientImpl + +#elif defined(ESP_GOOGLE_SHEET_CLIENT_WIFI_IS_AVAILABLE) +#include "WiFiClient.h" +#define BASE_WIFICLIENT WiFiClient #endif #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" @@ -101,7 +111,8 @@ class GS_TCP_Client : public Client _user = user; _password = password; _modem = modem; - _client_type = esp_google_sheet_client_type_external_gsm_client; + _basic_client = client; + _client_type = esp_google_sheet_client_type_external_gsm_client; #endif } @@ -119,11 +130,11 @@ class GS_TCP_Client : public Client _x509 = new X509List(caCert); _tcp_client->setTrustAnchors(_x509); - setCertType( esp_google_sheet_cert_type_data); + setCertType(esp_google_sheet_cert_type_data); } else { - setCertType( esp_google_sheet_cert_type_none); + setCertType(esp_google_sheet_cert_type_none); setInSecure(); } } @@ -163,11 +174,11 @@ class GS_TCP_Client : public Client _tcp_client->setTrustAnchors(_x509); _mbfs->delP(&der); - setCertType( esp_google_sheet_cert_type_file); + setCertType(esp_google_sheet_cert_type_file); } } - return getCertType() == esp_google_sheet_cert_type_file; + return getCertType() == esp_google_sheet_cert_type_file; } /** @@ -272,7 +283,7 @@ class GS_TCP_Client : public Client void ethDNSWorkAround(SPI_ETH_Module *eth, const char *host, uint16_t port) { - +#if defined(ESP_GOOGLE_SHEET_CLIENT_ETH_IS_AVAILABLE) if (!eth) return; @@ -300,10 +311,11 @@ class GS_TCP_Client : public Client #if defined(INC_ENC28J60_LWIP) || defined(INC_W5100_LWIP) || defined(INC_W5500_LWIP) ex: #if defined(ESP_GOOGLE_SHEET_CLIENT_WIFI_IS_AVAILABLE) - WiFiClient _client; + BASE_WIFICLIENT _client; _client.connect(host, port); _client.stop(); #endif +#endif #endif } @@ -316,7 +328,7 @@ class GS_TCP_Client : public Client // We will not invoke the network status request when device has built-in WiFi or Ethernet and it is connected. - if (_client_type == esp_google_sheet_client_type_external_gsm_client) + if (_client_type == esp_google_sheet_client_type_external_gsm_client) { _network_status = gprsConnected(); if (!_network_status) @@ -324,7 +336,7 @@ class GS_TCP_Client : public Client } else if (WiFI_CONNECTED || ethLinkUp()) _network_status = true; - else if (_client_type == esp_google_sheet_client_type_external_basic_client) + else if (_client_type == esp_google_sheet_client_type_external_basic_client) { if (!_network_status_cb) _last_error = 1; @@ -341,7 +353,7 @@ class GS_TCP_Client : public Client void networkReconnect() { - if (_client_type == esp_google_sheet_client_type_external_basic_client) + if (_client_type == esp_google_sheet_client_type_external_basic_client) { #if defined(ESP_GOOGLE_SHEET_CLIENT_HAS_WIFI_DISCONNECT) // We can reconnect WiFi when device connected via built-in WiFi that supports reconnect @@ -356,12 +368,12 @@ class GS_TCP_Client : public Client if (_network_connection_cb) _network_connection_cb(); } - else if (_client_type == esp_google_sheet_client_type_external_gsm_client) + else if (_client_type == esp_google_sheet_client_type_external_gsm_client) { gprsDisconnect(); gprsConnect(); } - else if (_client_type == esp_google_sheet_client_type_internal_basic_client) + else if (_client_type == esp_google_sheet_client_type_internal_basic_client) { #if defined(ESP_GOOGLE_SHEET_CLIENT_WIFI_IS_AVAILABLE) @@ -384,7 +396,7 @@ class GS_TCP_Client : public Client * Get the Client type. * @return The esp_google_sheet_client_type enum value. */ - esp_google_sheet_client_type type() { return _client_type; } + esp_google_sheet_client_type type() { return _client_type; } /** * Get the Client initialization status. @@ -395,16 +407,16 @@ class GS_TCP_Client : public Client bool rdy = true; #if !defined(ESP_GOOGLE_SHEET_CLIENT_WIFI_IS_AVAILABLE) - if (_client_type == esp_google_sheet_client_type_external_basic_client && + if (_client_type == esp_google_sheet_client_type_external_basic_client && (!_network_connection_cb || !_network_status_cb)) rdy = false; - else if (_client_type != esp_google_sheet_client_type_external_basic_client || - _client_type != esp_google_sheet_client_type_external_gsm_client) + else if (_client_type != esp_google_sheet_client_type_external_basic_client || + _client_type != esp_google_sheet_client_type_external_gsm_client) rdy = false; #else // assume external client is WiFiClient and network status request callback is not required // when device was connected to network using on board WiFi - if (_client_type == esp_google_sheet_client_type_external_basic_client && + if (_client_type == esp_google_sheet_client_type_external_basic_client && (!_network_connection_cb || (!_network_status_cb && !WiFI_CONNECTED && !ethLinkUp()))) { rdy = false; @@ -497,17 +509,17 @@ class GS_TCP_Client : public Client if (!_basic_client) { - if (_client_type == esp_google_sheet_client_type_external_basic_client) + if (_client_type == esp_google_sheet_client_type_external_basic_client) { _last_error = 1; return false; } - else if (_client_type != esp_google_sheet_client_type_external_gsm_client) + else if (_client_type != esp_google_sheet_client_type_external_gsm_client) { // Device has no built-in WiFi, external client required. #if defined(ESP_GOOGLE_SHEET_CLIENT_WIFI_IS_AVAILABLE) - _basic_client = new WiFiClient(); - _client_type = esp_google_sheet_client_type_internal_basic_client; + _basic_client = new BASE_WIFICLIENT(); + _client_type = esp_google_sheet_client_type_internal_basic_client; #else _last_error = 1; return false; @@ -521,16 +533,14 @@ class GS_TCP_Client : public Client return setError(ESP_GOOGLE_SHEET_CLIENT_ERROR_TCP_ERROR_CONNECTION_REFUSED); #if defined(ESP_GOOGLE_SHEET_CLIENT_WIFI_IS_AVAILABLE) && (defined(ESP32) || defined(ESP8266) || defined(MB_ARDUINO_PICO)) - if (_client_type == esp_google_sheet_client_type_internal_basic_client) - reinterpret_cast(_basic_client)->setNoDelay(true); + if (_client_type == esp_google_sheet_client_type_internal_basic_client) + reinterpret_cast(_basic_client)->setNoDelay(true); #endif // For TCP keepalive should work in ESP8266 core > 3.1.2. // https://github.com/esp8266/Arduino/pull/8940 - // Not currently supported by WiFiClientSecure in Arduino Pico core - - if (_client_type == esp_google_sheet_client_type_internal_basic_client) + if (_client_type == esp_google_sheet_client_type_internal_basic_client) { if (isKeepAliveSet()) { @@ -538,9 +548,9 @@ class GS_TCP_Client : public Client #if defined(ESP8266) if (_tcpKeepIdleSeconds == 0 || _tcpKeepIntervalSeconds == 0 || _tcpKeepCount == 0) - reinterpret_cast(_basic_client)->disableKeepAlive(); + reinterpret_cast(_basic_client)->disableKeepAlive(); else - reinterpret_cast(_basic_client)->keepAlive(_tcpKeepIdleSeconds, _tcpKeepIntervalSeconds, _tcpKeepCount); + reinterpret_cast(_basic_client)->keepAlive(_tcpKeepIdleSeconds, _tcpKeepIntervalSeconds, _tcpKeepCount); #elif defined(ESP32) @@ -786,9 +796,9 @@ class GS_TCP_Client : public Client _clock_ready = status; } - void setCertType( esp_google_sheet_cert_type type) { _cert_type = type; } + void setCertType(esp_google_sheet_cert_type type) { _cert_type = type; } - esp_google_sheet_cert_type getCertType() { return _cert_type; } + esp_google_sheet_cert_type getCertType() { return _cert_type; } unsigned long tcpTimeout() { @@ -813,10 +823,10 @@ class GS_TCP_Client : public Client void clear() { - if (_basic_client && _client_type == esp_google_sheet_client_type_internal_basic_client) + if (_basic_client && _client_type == esp_google_sheet_client_type_internal_basic_client) { #if defined(ESP_GOOGLE_SHEET_CLIENT_WIFI_IS_AVAILABLE) - delete (WiFiClient *)_basic_client; + delete (BASE_WIFICLIENT *)_basic_client; #else delete _basic_client; #endif @@ -824,7 +834,7 @@ class GS_TCP_Client : public Client } } - void setWiFi( esp_google_sheet_wifi *wifi) { _wifi_multi = wifi; } + void setWiFi(esp_google_sheet_wifi *wifi) { _wifi_multi = wifi; } bool gprsConnect() { @@ -909,10 +919,8 @@ class GS_TCP_Client : public Client uint32_t gprsGetTime() { #if defined(ESP_GOOGLE_SHEET_CLIENT_GSM_MODEM_IS_AVAILABLE) && defined(TINY_GSM_MODEM_HAS_TIME) - if (!gprsConnected()) return 0; - TinyGsm *gsmModem = (TinyGsm *)_modem; int year3 = 0; int month3 = 0; @@ -925,7 +933,8 @@ class GS_TCP_Client : public Client { if (gsmModem->getNetworkTime(&year3, &month3, &day3, &hour3, &min3, &sec3, &timezone)) { - return TimeHelper::getTimestamp(year3, month3, day3, hour3, min3, sec3); + //We have to subtract the local GSM network timezone to get GMT time + return TimeHelper::getTimestamp(year3, month3, day3, hour3, min3, sec3) - timezone * 3600; } } #endif @@ -937,7 +946,7 @@ class GS_TCP_Client : public Client #if defined(ESP32) && defined(ESP_GOOGLE_SHEET_CLIENT_WIFI_IS_AVAILABLE) // Actually we wish to use setSocketOption directly but it is ambiguous in old ESP32 core v1.0.x.; // Use setOption instead for old core support. - return reinterpret_cast(_basic_client)->setOption(option, value); + return reinterpret_cast(_basic_client)->setOption(option, value); #endif return 0; } @@ -965,7 +974,7 @@ class GS_TCP_Client : public Client MB_FS *_mbfs = nullptr; Client *_basic_client = nullptr; - esp_google_sheet_wifi *_wifi_multi = nullptr; + esp_google_sheet_wifi *_wifi_multi = nullptr; ESP_GOOGLE_SHEET_CLIENT_NetworkConnectionRequestCallback _network_connection_cb = NULL; ESP_GOOGLE_SHEET_CLIENT_NetworkStatusRequestCallback _network_status_cb = NULL; @@ -983,10 +992,10 @@ class GS_TCP_Client : public Client volatile bool _network_status = false; int _rx_size = 1024, _tx_size = 512; int *response_code = nullptr; - esp_google_sheet_auth_cfg_t *_config = nullptr; + esp_google_sheet_auth_cfg_t *_config = nullptr; - esp_google_sheet_cert_type _cert_type = esp_google_sheet_cert_type_undefined; - esp_google_sheet_client_type _client_type = esp_google_sheet_client_type_undefined; + esp_google_sheet_cert_type _cert_type = esp_google_sheet_cert_type_undefined; + esp_google_sheet_client_type _client_type = esp_google_sheet_client_type_undefined; #if defined(ESP8266) || defined(MB_ARDUINO_PICO) SPI_ETH_Module *eth = NULL; diff --git a/src/client/WiFiClientImpl.h b/src/client/WiFiClientImpl.h new file mode 100644 index 0000000..99be542 --- /dev/null +++ b/src/client/WiFiClientImpl.h @@ -0,0 +1,565 @@ +/** + * WiFiClientImpl v1.0.1 + * + * This library provides the base client in replacement of ESP32 WiFiClient. + * + * The WiFiClient in ESP32 cannot be used in multithreading environment as in FreeRTOS task + * which can (always) lead to the assetion error "pbuf_free: p->ref > 0". + * + * Created August 20, 2023 + * + * The MIT License (MIT) + * Copyright (c) 2022 K. Suwatchai (Mobizt) + * + * + * Permission is hereby granted, free of charge, to any person returning a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#if !defined(WIFICLIENT_IMPL_H) && defined(ESP32) +#define WIFICLIENT_IMPL_H + +#include +class WiFiClientImpl : public Client +{ +public: + WiFiClientImpl(){}; + virtual ~WiFiClientImpl() { tcpClose(); }; + int connect(IPAddress ip, uint16_t port) { return tcpConnect(ip, port, _timeout); } + int connect(IPAddress ip, uint16_t port, int32_t timeout_ms) { return tcpConnect(ip, port, timeout_ms); } + int connect(const char *host, uint16_t port) + { + IPAddress address((uint32_t)0); +#if defined(WIFI_HAS_HOST_BY_NAME) + if (!WiFiGenericClass::hostByName(host, address)) + return -1; +#endif + return tcpConnect(address, port, _timeout); + } + int connect(const char *host, uint16_t port, int32_t timeout_ms) + { + _timeout = timeout_ms; + return connect(host, port); + } + size_t write(uint8_t data) { return write(&data, 1); } + size_t write(const uint8_t *buf, size_t size) { return tcpWrite(buf, size); } + size_t write_P(PGM_P buf, size_t size) { return write(buf, size); } + size_t write(Stream &stream) + { + uint8_t *buf = (uint8_t *)malloc(1360); + if (!buf) + { + return 0; + } + size_t toRead = 0, toWrite = 0, written = 0; + size_t available = stream.available(); + while (available) + { + toRead = (available > 1360) ? 1360 : available; + toWrite = stream.readBytes(buf, toRead); + written += write(buf, toWrite); + available = stream.available(); + } + free(buf); + buf = nullptr; + return written; + } + int available() { return tcpAavailable(); } + int read() + { + uint8_t data = 0; + int res = read(&data, 1); + if (res < 0) + { + return res; + } + if (res == 0) + { // No data available. + return -1; + } + return data; + } + int read(uint8_t *buf, size_t size) { return tcpRead(buf, size); } + int peek() { return tcpPeek(); } + void flush() + { + if (r_available()) + fillRxBuffer(); + _fillPos = _fillSize; + } + + void stop() { tcpClose(); } + uint8_t connected() { return tcpConnected(); } + + operator bool() + { + return connected(); + } + WiFiClientImpl &operator=(const WiFiClientImpl &other); + bool operator==(const bool value) + { + return bool() == value; + } + bool operator!=(const bool value) + { + return bool() != value; + } + bool operator==(const WiFiClientImpl &); + bool operator!=(const WiFiClientImpl &rhs) + { + return !this->operator==(rhs); + }; + + virtual int fd() const { return _socket; } + + int setSocketOption(int option, char *value, size_t len) + { + return setSocketOption(SOL_SOCKET, option, (const void *)value, len); + } + int setSocketOption(int level, int option, const void *value, size_t len) + { + int res = setsockopt(_socket, level, option, value, len); + if (res < 0) + { + log_e("fail on %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); + } + return res; + } + int setOption(int option, int *value) + { + return setSocketOption(IPPROTO_TCP, option, (const void *)value, sizeof(int)); + } + int getOption(int option, int *value) + { + socklen_t size = sizeof(int); + int res = getsockopt(_socket, IPPROTO_TCP, option, (char *)value, &size); + if (res < 0) + { + log_e("fail on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); + } + return res; + } + + int setTimeout(uint32_t seconds) + { + Client::setTimeout(seconds * 1000); // This should be here? + _timeout = seconds * 1000; + if (_socket >= 0) + { + struct timeval tv; + tv.tv_sec = seconds; + tv.tv_usec = 0; + if (setSocketOption(SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)) < 0) + { + return -1; + } + return setSocketOption(SO_SNDTIMEO, (char *)&tv, sizeof(struct timeval)); + } + else + { + return 0; + } + } + + int setNoDelay(bool nodelay) + { + int flag = nodelay; + return setOption(TCP_NODELAY, &flag); + } + + bool getNoDelay() + { + int flag = 0; + getOption(TCP_NODELAY, &flag); + return flag; + } + + IPAddress remoteIP() const + { + return remoteIP(_socket); + } + + IPAddress remoteIP(int fd) const + { + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getpeername(fd, (struct sockaddr *)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return IPAddress((uint32_t)(s->sin_addr.s_addr)); + } + + uint16_t remotePort() const + { + return remotePort(_socket); + } + + uint16_t remotePort(int fd) const + { + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getpeername(fd, (struct sockaddr *)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return ntohs(s->sin_port); + } + + IPAddress localIP() const + { + return localIP(_socket); + } + + IPAddress localIP(int fd) const + { + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getsockname(fd, (struct sockaddr *)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return IPAddress((uint32_t)(s->sin_addr.s_addr)); + } + + uint16_t localPort() const + { + return localPort(_socket); + } + + uint16_t localPort(int fd) const + { + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getsockname(fd, (struct sockaddr *)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return ntohs(s->sin_port); + } + + // friend class WiFiServer; + using Print::write; + +private: + int _socket = -1; + int _timeout = 30000; + size_t _rxBuffSize = 2048; + uint8_t *_rxBuff = nullptr; + size_t _fillPos = 0; + size_t _fillSize = 0; + bool _failed = false; + bool _connected = false; + + size_t r_available() + { + if (_socket < 0) + { + return 0; + } + int count; +#ifdef ESP_IDF_VERSION_MAJOR + int res = lwip_ioctl(_socket, FIONREAD, &count); +#else + int res = lwip_ioctl_r(_socket, FIONREAD, &count); +#endif + if (res < 0) + { + _failed = true; + return 0; + } + return count; + } + + size_t fillRxBuffer() + { + if (!_rxBuff && !allocRxBuffer(_rxBuffSize)) + return 0; + + if (_fillSize && _fillPos == _fillSize) + { + _fillSize = 0; + _fillPos = 0; + } + + if (!_rxBuff || _rxBuffSize <= _fillSize || !r_available()) + { + return 0; + } + int res = recv(_socket, _rxBuff + _fillSize, _rxBuffSize - _fillSize, MSG_DONTWAIT); + if (res < 0) + { + if (errno != EWOULDBLOCK) + { + _failed = true; + } + return 0; + } + _fillSize += res; + return res; + } + + bool failed() + { + return _failed; + } + + int tcpConnect(const IPAddress &ip, uint32_t port, int timeout) + { + int enable = 1; + + log_v("Starting socket"); + + _socket = -1; + + _socket = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (_socket < 0) + { + return _socket; + } + + struct sockaddr_in serv_addr; + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = ip; + serv_addr.sin_port = htons(port); + + if (timeout <= 0) + timeout = 30000; // Milli seconds. + + fd_set fdset; + struct timeval tv; + FD_ZERO(&fdset); + FD_SET(_socket, &fdset); + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + int res = lwip_connect(_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); + + if (res < 0 && errno != EINPROGRESS) + { + log_e("connect on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); + tcpClose(); + return -1; + } + + res = select(_socket + 1, nullptr, &fdset, nullptr, timeout < 0 ? nullptr : &tv); + if (res < 0) + { + log_e("select on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); + tcpClose(); + return -1; + } + else if (res == 0) + { + log_i("select returned due to timeout %d ms for fd %d", timeout, _socket); + tcpClose(); + return -1; + } + else + { + int sockerr; + socklen_t len = (socklen_t)sizeof(int); + res = getsockopt(_socket, SOL_SOCKET, SO_ERROR, &sockerr, &len); + + if (res < 0) + { + log_e("getsockopt on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); + tcpClose(); + return -1; + } + + if (sockerr != 0) + { + log_e("socket error on fd %d, errno: %d, \"%s\"", _socket, sockerr, strerror(sockerr)); + tcpClose(); + return -1; + } + } + + lwip_setsockopt(_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + lwip_setsockopt(_socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + + lwip_setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)); + lwip_setsockopt(_socket, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)); + + fcntl(_socket, F_SETFL, fcntl(_socket, F_GETFL, 0) | O_NONBLOCK); + + _connected = true; + + return 1; + } + + void tcpClose() + { + lwip_close(_socket); + _socket = -1; + _connected = false; + freeRxBuffer(); + } + + int tcpAavailable() + { + return _fillSize - _fillPos + r_available(); + } + + int tcpPeek() + { + if (!_rxBuff || _fillPos == _fillSize && !fillRxBuffer()) + { + return -1; + } + return _rxBuff[_fillPos]; + } + + size_t tcpWrite(const uint8_t *buf, size_t size) + { + if (!tcpConnected() || !size) + return 0; + + int res = 0; + int retry = 10; + int socketFileDescriptor = _socket; + size_t totalBytesSent = 0; + size_t bytesRemaining = size; + + while (retry) + { + // use select to make sure the socket is ready for writing + fd_set set; + struct timeval tv; + FD_ZERO(&set); // empties the set + FD_SET(socketFileDescriptor, &set); // adds FD to the set + tv.tv_sec = 0; + tv.tv_usec = 1000000; + retry--; + + if (select(socketFileDescriptor + 1, NULL, &set, NULL, &tv) < 0) + { + return 0; + } + + if (FD_ISSET(socketFileDescriptor, &set)) + { + res = send(socketFileDescriptor, (void *)buf, bytesRemaining, MSG_DONTWAIT); + if (res > 0) + { + totalBytesSent += res; + if (totalBytesSent >= size) + { + // completed successfully + retry = 0; + } + else + { + buf += res; + bytesRemaining -= res; + retry = 10; + } + } + else if (res < 0) + { + log_e("fail on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); + if (errno != EAGAIN) + { + // if resource was busy, can try again, otherwise give up + res = 0; + retry = 0; + } + } + else + { + // Try again + } + } + } + return totalBytesSent; + + // return ssize_t or signed size_t for error + return lwip_write(_socket, buf, size); + } + + size_t tcpRead(uint8_t *dst, size_t len) + { + + if (!dst || !len || (_fillPos == _fillSize && !fillRxBuffer())) + { + return _failed ? -1 : 0; + } + + int remain = _fillSize - _fillPos; + if (len <= remain || ((len - remain) <= (_rxBuffSize - _fillSize) && fillRxBuffer() >= (len - remain))) + { + if (len == 1) + { + *dst = _rxBuff[_fillPos]; + } + else + { + memcpy(dst, _rxBuff + _fillPos, len); + } + _fillPos += len; + return len; + } + + size_t left = len; + size_t toRead = remain; + uint8_t *buf = dst; + memcpy(buf, _rxBuff + _fillPos, toRead); + _fillPos += toRead; + left -= toRead; + buf += toRead; + while (left) + { + if (!fillRxBuffer()) + { + return len - left; + } + remain = _fillSize - _fillPos; + toRead = (remain > left) ? left : remain; + memcpy(buf, _rxBuff + _fillPos, toRead); + _fillPos += toRead; + left -= toRead; + buf += toRead; + } + return len; + } + + int tcpConnected() + { + return _socket >= 0; + } + + bool allocRxBuffer(size_t size) + { + if (_rxBuff) + freeRxBuffer(); + + _rxBuff = (uint8_t *)malloc(size); + if (!_rxBuff) + { + + log_e("Not enough memory to allocate buffer"); + _failed = true; + return false; + } + + return true; + } + + void freeRxBuffer() + { + if (_rxBuff) + free(_rxBuff); + + _rxBuff = nullptr; + } +}; + +#endif /* WIFICLIENT_IMPL_H */