diff --git a/Json Models/GSheets JSON.md b/Json Models/GSheets JSON.md new file mode 100644 index 00000000..46647976 --- /dev/null +++ b/Json Models/GSheets JSON.md @@ -0,0 +1,47 @@ +# GSheets JSON + +``` +{ + "Beer":"xxxxxxxxxxxxxxxxxxxxxxxxx", + "Temp":"xxxxx", + "SG":"xxxxxxx", + "Color":"xxxxxxx", + "Comment":"", + "Email":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "tzOffset":-12 +} +``` + +## Serialize: + +``` +StaticJsonDocument<512> doc; + +doc["Beer"] = "xxxxxxxxxxxxxxxxxxxxxxxxx"; +doc["Temp"] = "xxxxx"; +doc["SG"] = "xxxxxxx"; +doc["Color"] = "xxxxxxx"; +doc["Comment"] = ""; +doc["Email"] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; +doc["tzOffset"] = -12; + +String output; +serializeJson(doc, output); +``` + +## Deserialize: + +``` +// String input; + +StaticJsonDocument<512> doc; +deserializeJson(doc, input); + +const char* Beer = doc["Beer"]; // "xxxxxxxxxxxxxxxxxxxxxxxxx" +const char* Temp = doc["Temp"]; // "xxxxx" +const char* SG = doc["SG"]; // "xxxxxxx" +const char* Color = doc["Color"]; // "xxxxxxx" +const char* Comment = doc["Comment"]; // "" +const char* Email = doc["Email"]; +int tzOffset = doc["tzOffset"]; // -12 +``` diff --git a/platformio.ini b/platformio.ini index 69dd3f68..9106cc42 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,23 +12,24 @@ ; Added this here to "hard code" the environment for macro debugging/visuals ; default_envs = lcd_ssd1306 ; default_envs = d32_pro_tft -; default_envs = tft_espi +default_envs = tft_espi [common] platform = espressif32 +platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git framework = arduino ; As the framework has grown, min_spiffs no longer has enough space. We're now ; requiring 16MB of flash space for most builds (which comes standard with the ; recommended D32 Pro) board_build.partitions = large_spiffs_16MB.csv -upload_speed = 460800 +upload_speed = 921600 ; TODO: 460800 monitor_speed = 115200 monitor_filters = esp32_exception_decoder ; log2file -; Ths can/will be set in tools/get_port.py -; upload_port = -; monitor_port = +; This can/will be set in tools/get_port.py +; upload_port = +; monitor_port = monitor_dtr = 0 monitor_rts = 0 ; -D_GLIBCXX_USE_C99 is to fix an issue with the xtensa toolchain that precludes the use of std::to_string @@ -57,9 +58,11 @@ lib_deps = https://github.com/lbussy/esptelnet.git https://github.com/h2zero/NimBLE-Arduino.git https://github.com/256dpi/arduino-mqtt.git - https://github.com/BlueAndi/ESPAsyncWebServer.git#master - https://github.com/lbussy/AsyncWiFiManager.git#development + https://github.com/me-no-dev/ESPAsyncWebServer + ;https://github.com/BlueAndi/ESPAsyncWebServer.git#master + https://github.com/tzapu/WiFiManager.git https://github.com/lbussy/LCBUrl.git#devel + https://github.com/bblanchon/ArduinoStreamUtils.git ; This is used to allow stream processing, can be turned off later. build_type = debug ; release ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -69,6 +72,7 @@ build_type = debug ; release [env:lcd_ssd1306] ; Small OLED Board board = lolin_d32 platform = ${common.platform} +platform_packages = ${common.platform_packages} framework = ${common.framework} ; For the "OLED" variant, we can't guarantee we have more than 4MB of flash. Use ; huge_app to get us the space - but at the cost of being able to update OTA. @@ -76,7 +80,7 @@ board_build.partitions = huge_app.csv upload_speed = ${common.upload_speed} monitor_speed = ${common.monitor_speed} monitor_filters = ${common.monitor_filters} -; Ths can/will be set in tools/get_port.py +; This can/will be set in tools/get_port.py ; upload_port = ${common.upload_port} ; monitor_port = ${common.monitor_port} monitor_dtr = ${common.monitor_dtr} @@ -93,12 +97,13 @@ build_type = ${common.build_type} [env:d32_pro_tft] ; Lolin ESP32 + ILI TFT board = lolin_d32_pro platform = ${common.platform} +platform_packages = ${common.platform_packages} framework = ${common.framework} board_build.partitions = ${common.board_build.partitions} upload_speed = ${common.upload_speed} monitor_speed = ${common.monitor_speed} monitor_filters = ${common.monitor_filters} -; Ths can/will be set in tools/get_port.py +; This can/will be set in tools/get_port.py ; upload_port = {common.upload_port} ; monitor_port = ${common.monitor_port} monitor_dtr = ${common.monitor_dtr} @@ -119,7 +124,7 @@ build_flags = -DTFT_RST=33 -DSPI_FREQUENCY=40000000 -DSPI_READ_FREQUENCY=20000000 - -DSPI_TOUCH_FREQUENCY=2500000 + -DSPI_TOUCH_FREQUENCY=2500000 -DSMOOTH_FONT=1 -DTFT_BL=32 -DTFT_BACKLIGHT_ON=1 @@ -138,6 +143,7 @@ build_type = ${common.build_type} ; https://github.com/Bodmer/TFT_eSPI board = esp32dev platform = ${common.platform} +platform_packages = ${common.platform_packages} framework = ${common.framework} ; The T-Display has 4MB of flash. Use huge_app to get us the space - but ; at the cost of being able to update OTA. @@ -145,7 +151,7 @@ board_build.partitions = huge_app.csv upload_speed = ${common.upload_speed} monitor_speed = ${common.monitor_speed} monitor_filters = ${common.monitor_filters} -; Ths can/will be set in tools/get_port.py +; This can/will be set in tools/get_port.py ; upload_port = -DUSBC ; monitor_port = -DUSBC monitor_dtr = ${common.monitor_dtr} @@ -170,4 +176,4 @@ build_flags = lib_deps = ${common.lib_deps} TFT_eSPI -build_type = ${common.build_type} \ No newline at end of file +build_type = ${common.build_type} diff --git a/src/SecureWithRedirects.cpp b/src/SecureWithRedirects.cpp deleted file mode 100644 index 25d93261..00000000 --- a/src/SecureWithRedirects.cpp +++ /dev/null @@ -1,133 +0,0 @@ -// -// Created by John Beeler on 4/13/20. -// - -#include "SecureWithRedirects.h" - -SecureWithRedirects::SecureWithRedirects(const char *original_url, const char *api_key, const char *data_to_send, const char *content_type) -{ - // Initialize secure_client & HTTPClient - secure_client = new WiFiClientSecure; - https = new HTTPClient; - - // Set defaults - redirects = 0; - use_get = false; - - // Configure the HTTPClient to collect headers - const char *headerNames[] = {"Location"}; - https->collectHeaders(headerNames, sizeof(headerNames) / sizeof(headerNames[0])); - - // Set the URL/apiKey/dataToSend - url = original_url; - apiKey = api_key; - dataToSend = data_to_send; - contentType = content_type; -} - -void SecureWithRedirects::end() -{ - // Delete secure_client/https and free memory - secure_client->stop(); - delete https; - delete secure_client; -} - -bool SecureWithRedirects::send_with_redirects() -{ -#ifdef ARDUINO_LOG_LEVEL - String response; - String location_header; -#endif - int httpResponseCode; - - if (redirects >= MAXIMUM_REDIRECTS) - { // We've been redirected too many times - Log.error(F("[SWR::send_with_redirects] Too many redirects - returning" CR)); - return false; - } - Log.verbose(F("[SWR::send_with_redirects] Current URL: %s" CR), url.c_str()); - - https->begin(*secure_client, url); - https->addHeader("Content-Type", contentType); //Specify content-type header - if (apiKey) - { - https->addHeader("X-API-KEY", apiKey); //Specify API key header - } - if (use_get) - httpResponseCode = https->GET(); // The redirect type we got implies we shouldn't re-send the POST data - else - httpResponseCode = https->POST(dataToSend); //Send the actual POST request - - switch (httpResponseCode) - { - case HTTP_CODE_OK: - case HTTP_CODE_CREATED: - case HTTP_CODE_ACCEPTED: - case HTTP_CODE_NON_AUTHORITATIVE_INFORMATION: - case HTTP_CODE_NO_CONTENT: - case HTTP_CODE_PARTIAL_CONTENT: - // These are success codes - Return true, as we were able to post - Log.verbose(F("[SWR::send_with_redirects] Success - Code %d, %s" CR), httpResponseCode, https->getString().c_str()); - https->end(); - return true; - case HTTP_CODE_FOUND: - case HTTP_CODE_SEE_OTHER: - // These are redirect codes (which is the whole reason that this function exists) - Log.verbose(F("[SWR::send_with_redirects] Redirected (use get next time) - Code %d, %s" CR), httpResponseCode, https->getString().c_str()); - - if (https->hasHeader("Location")) - { - Log.verbose(F("[SWR::send_with_redirects] Location Header: %s" CR), https->header("Location").c_str()); - - url = https->header("Location"); - https->end(); - redirects++; // Increment redirects - use_get = true; // For these redirect codes, we have to switch to use GET (without post data) - return send_with_redirects(); // Then call send_with_redirects again - } - else - { - Log.verbose(F("[SWR::send_with_redirects] No location header found." CR)); - https->end(); - return false; - } - case HTTP_CODE_MOVED_PERMANENTLY: - case HTTP_CODE_TEMPORARY_REDIRECT: - case HTTP_CODE_PERMANENT_REDIRECT: - // These are redirect codes (which is the whole reason that this function exists) - Log.verbose(F("[SWR::send_with_redirects] Redirected - Code %d, %s" CR), httpResponseCode, https->getString().c_str()); - - if (https->hasHeader("Location")) - { - - Log.verbose(F("[SWR::send_with_redirects] Location Header: %s" CR), https->header("Location").c_str()); - - url = https->header("Location"); - https->end(); - redirects++; // Increment redirects - return send_with_redirects(); // Then call send_with_redirects again - } - else - { - Log.verbose(F("[SWR::send_with_redirects] No location header found." CR)); - https->end(); - return false; - } - - case HTTPC_ERROR_CONNECTION_LOST: - case HTTPC_ERROR_CONNECTION_REFUSED: - case HTTPC_ERROR_NOT_CONNECTED: - case HTTPC_ERROR_READ_TIMEOUT: - // We had some kind of connection issue. Treat it as a redirect, but keep the same URL/method - Log.error(F("[SWR::send_with_redirects] Connection error - Code %d, %s" CR), httpResponseCode, https->getString().c_str()); - - https->end(); - redirects++; // Increment redirects - return send_with_redirects(); // Then call send_with_redirects again - default: - Log.error(F("[SWR::send_with_redirects] Failed - Code %d, %s" CR), httpResponseCode, https->getString().c_str()); - https->end(); - return false; - } -} diff --git a/src/SecureWithRedirects.h b/src/SecureWithRedirects.h deleted file mode 100644 index 72ff5c46..00000000 --- a/src/SecureWithRedirects.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// Created by John Beeler on 4/13/20. -// - -#ifndef TILTBRIDGE_SECUREWITHREDIRECTS_H -#define TILTBRIDGE_SECUREWITHREDIRECTS_H - -#include "serialhandler.h" -#include "tiltBridge.h" -#include -#include -#include -#include -#include -#include -#include - -#define MAXIMUM_REDIRECTS 10 - -class SecureWithRedirects -{ - -public: - SecureWithRedirects(const char *original_url, const char *api_key, const char *data_to_send, const char *content_type); - void end(); - bool send_with_redirects(); - -private: - int redirects; - bool use_get; // When Google Scripts returns its first redirect, it typically uses an error code that prevents re-POSTing the data - WiFiClientSecure *secure_client; - HTTPClient *https; - - String url; - const char *apiKey; - const char *dataToSend; - const char *contentType; -}; - -#endif //TILTBRIDGE_SECUREWITHREDIRECTS_H diff --git a/src/bridge_lcd.cpp b/src/bridge_lcd.cpp index 76327262..0df85ece 100644 --- a/src/bridge_lcd.cpp +++ b/src/bridge_lcd.cpp @@ -40,7 +40,7 @@ void bridge_lcd::display_logo() (64 - oled_logo_height) / 2, oled_logo_width, oled_logo_height, - tiltbridge_logo_bits); + oled_logo_bits); display(); #endif diff --git a/src/http_server.cpp b/src/http_server.cpp index e60bd9af..f68fd0ad 100644 --- a/src/http_server.cpp +++ b/src/http_server.cpp @@ -143,7 +143,7 @@ bool processTiltBridgeSettings(AsyncWebServerRequest *request) { // We reset hostname, process hostnamechanged = false; http_server.name_reset_requested = true; - Log.verbose(F("POSTed new mDNSid, queued network reset." CR)); + Log.notice(F("POSTed new mDNSid, queued network reset." CR)); } return true; } @@ -1250,6 +1250,5 @@ void httpServer::init() DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); server.begin(); - Log.notice(F("Async HTTP server started." CR)); - Log.verbose(F("Open: http://%s.local/ to view application." CR), WiFi.getHostname()); + Log.notice(F("HTTP server started. Open: http://%s.local/ to view application." CR), WiFi.getHostname()); } diff --git a/src/main.cpp b/src/main.cpp index e2f53d5d..11561e85 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,43 +25,37 @@ void setup() loadConfig(); Log.verbose(F("Initializing LCD." CR)); - // Handle setting the display up lcd.init(); // Initialize the display Log.verbose(F("Initializing WiFi." CR)); init_wifi(); // Initialize WiFi (including configuration AP if necessary) initWiFiResetButton(); - // I kind of want to leave the WiFi info on screen longer here instead of the logo. The logo will display often - // enough as-is. - // lcd.display_logo(); // Display the logo - #ifdef LOG_LOCAL_LEVEL esp_log_level_set("*", ESP_LOG_DEBUG); // Set all components to debug level esp_log_level_set("wifi", ESP_LOG_WARN); // Enable WARN logs from WiFi stack esp_log_level_set("dhcpc", ESP_LOG_WARN); // Enable WARN logs from DHCP client #endif - // Initialize the BLE scanner - tilt_scanner.init(); - tilt_scanner.scan(); - data_sender.init(); // Initialize the data sender - data_sender.init_mqtt(); //Initialize the mqtt server connection if configured. + Log.verbose(F("Initializing scanner." CR)); + tilt_scanner.init(); // Initialize the BLE scanner + tilt_scanner.wait_until_scan_complete(); // Wait until the initial scan completes - // Once all this is done, we'll wait until the initial scan completes. - tilt_scanner.wait_until_scan_complete(); - http_server.init(); + data_sender.init(); // Initialize the data sender + http_server.init(); // Initialize the web server - memCheck.attach(30, printMem); // Memory debug print + memCheck.attach(30, printMem); // Memory debug print on timer } void loop() { serialLoop(); // Service telnet and console commands - // The scans are done asynchronously, so we'll poke the scanner to see if a new scan needs to be triggered. if (tilt_scanner.scan()) { + // The scans are done asynchronously, so we'll poke the scanner to see if + // a new scan needs to be triggered. + // If we need to do anything when a new scan is started, trigger it here. } @@ -116,6 +110,4 @@ void loop() http_server.lcd_reinit_rqd = false; lcd.reinit(); } - - yield(); } diff --git a/src/sendData.cpp b/src/sendData.cpp index ef6e5b1f..7925ef5b 100644 --- a/src/sendData.cpp +++ b/src/sendData.cpp @@ -6,6 +6,7 @@ dataSendHandler data_sender; // Global data sender +WiFiClient client; WiFiClient wClient; MQTTClient mqttClient(256); @@ -24,25 +25,9 @@ dataSendHandler::dataSendHandler() mqtt_alreadyinit = false; } -void dataSendHandler::setClock() -{ - configTime(0, 0, "pool.ntp.org", "time.nist.gov"); - - time_t nowSecs = time(nullptr); - while (nowSecs < 8 * 3600 * 2) - { - delay(500); - yield(); - nowSecs = time(nullptr); - } - - struct tm timeinfo; - gmtime_r(&nowSecs, &timeinfo); -} - void dataSendHandler::init() { - setClock(); + init_mqtt(); } void dataSendHandler::init_mqtt() @@ -111,13 +96,6 @@ bool dataSendHandler::send_to_localTarget() // TODO: (JSON) Come back and tighten this up bool result = true; DynamicJsonDocument j(TILT_ALL_DATA_SIZE + 128); - // This should look like this when sent to Fermentrack: - // { - // 'mdns_id': 'mDNS ID Goes Here', - // 'tilts': {'color': 'Purple', 'temp': 74, 'gravity': 1.043}, - // {'color': 'Orange', 'temp': 66, 'gravity': 1.001} - // } - char payload[TILT_ALL_DATA_STRING_SIZE + 128]; j["mdns_id"] = config.mdnsID; @@ -165,95 +143,87 @@ bool dataSendHandler::send_to_brewstatus() return result; } -// For sending data to Google Scripts, we have to use secure_client but otherwise we're doing the same thing as before. -bool dataSendHandler::send_to_url_https(const char *url, const char *apiKey, const char *dataToSend, const char *contentType) -{ - // This handles the generic act of sending data to an endpoint - bool result = false; - - if (strlen(dataToSend) > 5) - { - Log.verbose(("[HTTPS] Sending data to: %s." CR), url); - Log.verbose(F("Data to send: %s" CR), dataToSend); - Log.verbose(F("[HTTPS] Pre-deinit RAM left %d" CR), esp_get_free_heap_size()); - - // We're severely memory starved. Deinitialize bluetooth and free the related memory - // NOTE - This is not strictly true under NimBLE. Deinit now only waits for a scan to complete - tilt_scanner.deinit(); - yield(); - - Log.verbose(F("[HTTPS] Post-deinit RAM left %d" CR), esp_get_free_heap_size()); - Log.verbose(F("[HTTPS] Calling SWR::send_with_redirects." CR)); - - SecureWithRedirects SWR(url, apiKey, dataToSend, contentType); - result = SWR.send_with_redirects(); - SWR.end(); - yield(); - Log.verbose(F("[HTTPS] Post-SWR RAM left %d" CR), esp_get_free_heap_size()); - - tilt_scanner.init(); - yield(); - Log.verbose(F("[HTTPS] Post-Reinit RAM left %d" CR), esp_get_free_heap_size()); - } - return result; -} - bool dataSendHandler::send_to_google() { - // TODO: (JSON) Come back and tighten this up HTTPClient http; - StaticJsonDocument<750> payload; - + WiFiClientSecure secureClient; + int httpResponseCode; + int numSent = 0; bool result = true; - // There are two configuration options which are mandatory when using the Google Sheets integration - if (strlen(config.scriptsURL) <= GSCRIPTS_MIN_URL_LENGTH || - strlen(config.scriptsEmail) < GSCRIPTS_MIN_EMAIL_LENGTH) - { - // Log.verbose(D("Either scriptsURL or scriptsEmail not populated. Returning." CR)); - return false; - } - - // This should look like this when sent to the proxy that sends to Google (once per Tilt): - // { - // 'payload': { // Payload is what gets ultimately sent on to Google Scripts - // 'Beer': 'Key Goes Here', - // 'Temp': 65, - // 'SG': 1.050, // This is sent as a float - // 'Color': 'Blue', - // 'Comment': '', - // 'Email': 'xxx@gmail.com', - // }, - // 'gscripts_url': 'https://script.google.com/.../', // This is specific to the proxy - // } - // - // For secure GScripts support, we don't send the 'j' json object - just the payload. - - // Loop through each of the tilt colors cached by tilt_scanner, sending data for each of the active tilts for (uint8_t i = 0; i < TILT_COLORS; i++) { + // Loop through each of the tilt colors cached by tilt_scanner if (tilt_scanner.tilt(i)->is_loaded()) { - if (tilt_scanner.tilt(i)->gsheets_beer_name().length() <= 0) + // If there is a color present + if (tilt_scanner.tilt(i)->gsheets_beer_name().length() > 0) { - continue; // If there is no gsheets beer name, we don't know where to log to. Skip this tilt. - } + if (numSent == 0) + Log.notice(F("Beginning GSheets check-in." CR)); + // If there's a sheet name saved + StaticJsonDocument payload; + payload["Beer"] = tilt_scanner.tilt(i)->gsheets_beer_name(); + payload["Temp"] = tilt_scanner.tilt(i)->converted_temp(true); // Always in Fahrenheit + payload["SG"] = tilt_scanner.tilt(i)->converted_gravity(false); + payload["Color"] = tilt_scanner.tilt(i)->color_name(); + payload["Comment"] = ""; + payload["Email"] = config.scriptsEmail; // The gmail email address associated with the script on google + payload["tzOffset"] = config.TZoffset; + + char payload_string[GSHEETS_JSON]; + serializeJson(payload, payload_string); + payload.clear(); - payload["Beer"] = tilt_scanner.tilt(i)->gsheets_beer_name(); - payload["Temp"] = tilt_scanner.tilt(i)->converted_temp(true); // Always in Fahrenheit - payload["SG"] = tilt_scanner.tilt(i)->converted_gravity(false); - payload["Color"] = tilt_scanner.tilt(i)->color_name(); - payload["Comment"] = ""; - payload["Email"] = config.scriptsEmail; // The gmail email address associated with the script on google - payload["tzOffset"] = config.TZoffset; + http.useHTTP10(true); // Turn off chunked transfer encoding to parse the stream + http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); // Follow the 301 + http.setConnectTimeout(3000); // Set 3 second timeout + secureClient.setInsecure(); // Ignore SHA fingerprint - char payload_string[500]; - serializeJson(payload, payload_string); - // When sending the data to GScripts directly, we're sending the payload - not the wrapped payload - if (!send_to_url_https(config.scriptsURL, "", payload_string, "application/json")) - result = false; // There was an error with the previous send - } - } + if (! http.begin(secureClient, config.scriptsURL)) // Connect secure + { + Log.error(F("Unable to create secure connection to %s." CR), config.scriptsURL); + result = false; + } // Failed to open a connection + else + { + Log.verbose(F("Created secure connection to %s." CR), config.scriptsURL); + Log.verbose(F("Sending the following payload to Google Sheets (%s):\n\t\t%s" CR), tilt_scanner.tilt(i)->color_name().c_str(), payload_string); + + http.addHeader(F("Content-Type"), F("application/json")); // Specify content-type header + httpResponseCode = http.POST(payload_string); // Send the payload + if (httpResponseCode == 200) + { + // POST success + StaticJsonDocument retval; +#if (ARDUINO_LOG_LEVEL == 6) + Log.verbose(F("JSON Response:" CR)); + ReadLoggingStream loggingStream(http.getStream(), Serial); + deserializeJson(retval, loggingStream); + Serial.println(); +#else + deserializeJson(retval, http.getStream()); +#endif + Log.verbose(F("DEBUG: The link is: %s" CR), retval["doclongurl"].as().c_str()); // TODO: Save this + retval.clear(); + numSent++; + } // Response code = 200 + else + { + // Post generated an error + Log.error(F("Google send to %s Tilt failed (%d): %s. Response:\n%s" CR), + tilt_scanner.tilt(i)->color_name().c_str(), + httpResponseCode, + http.errorToString(httpResponseCode).c_str(), + http.getString().c_str()); + result = false; + } // Response code != 200 + } // Good connection + http.end(); + } // Check we have a sheet name for the color + } // Check scanner is loaded for color + } // Loop through colors + Log.notice(F("Submitted %l sheet%s to Google." CR), numSent, (numSent== 1) ? "" : "s"); return result; } @@ -299,16 +269,6 @@ bool dataSendHandler::send_to_bf_and_bf(const uint8_t which_bf) return false; } - // The data should look like this when sent to Brewfather or Brewers Friend (once per Tilt): - // { - // 'name': 'Red', // The color of the Tilt - // 'temp': 65, - // 'temp_unit': 'F', // Always in Fahrenheit - // 'gravity': 1.050, // This is sent as a float - // 'gravity_unit': 'G', // We send specific gravity, not plato - // 'device_source': 'TiltBridge', - // } - // Loop through each of the tilt colors cached by tilt_scanner, sending data for each of the active tilts for (uint8_t i = 0; i < TILT_COLORS; i++) { @@ -358,7 +318,7 @@ bool dataSendHandler::send_to_url(const char *url, const char *apiKey, const cha if (lcburl.getScheme() == "http") validTarget = true; } - + if (validTarget) { if (lcburl.isMDNS(lcburl.getHost().c_str())) @@ -371,14 +331,6 @@ bool dataSendHandler::send_to_url(const char *url, const char *apiKey, const cha Log.notice(F("Connecting to: %s on port %l" CR), lcburl.getHost().c_str(), lcburl.getPort()); - - WiFiClient client; - // 1 = SUCCESS - // 0 = FAILED - // -1 = TIMED_OUT - // -2 = INVALID_SERVER - // -3 = TRUNCATED - // -4 = INVALID_RESPONSE client.setNoDelay(true); client.setTimeout(10000); if (client.connect(lcburl.getIP(lcburl.getHost().c_str()), lcburl.getPort())) @@ -483,14 +435,12 @@ bool dataSendHandler::send_to_url(const char *url, const char *apiKey, const cha { Log.error(F("Invalid target: %s." CR), url); } - } else { Log.notice(F("No URL provided, or no data to send." CR)); retVal = false; } - return retVal; } @@ -629,7 +579,7 @@ void send_checkin_stat() void dataSendHandler::process() { - // dataSendHandler::process() processes each tick & dispatches HTTP clients to push data out as necessary + // Processes each tick & dispatches HTTP clients to push data out as necessary if (http_server.name_reset_requested == true) // Don't send while we are processing a name change return; @@ -673,16 +623,19 @@ void dataSendHandler::process() // Check & send to Google Scripts if necessary if (send_to_google_at <= xTaskGetTickCount()) { - if (strlen(config.scriptsURL) > GSCRIPTS_MIN_URL_LENGTH) + if (strlen(config.scriptsURL) >= GSCRIPTS_MIN_URL_LENGTH && + strlen(config.scriptsEmail) >= GSCRIPTS_MIN_EMAIL_LENGTH) { - Log.verbose(F("Calling send to Google." CR)); - // tilt_scanner.wait_until_scan_complete(); + Log.verbose(F("Checking for any pending Google Sheets pushes." CR)); + tilt_scanner.deinit(); + yield(); send_to_google(); + tilt_scanner.init(); send_to_google_at = xTaskGetTickCount() + GSCRIPTS_DELAY; } else { - // If the user adds the setting, we want this to kick in within 10 seconds + // If the user newly adds the setting, this allows it to trigger within 10 seconds send_to_google_at = xTaskGetTickCount() + 10000; } yield(); diff --git a/src/sendData.h b/src/sendData.h index 6fe46932..53e1f6be 100644 --- a/src/sendData.h +++ b/src/sendData.h @@ -8,11 +8,15 @@ #include "serialhandler.h" #include "tiltBridge.h" #include "wifi_setup.h" -#include "SecureWithRedirects.h" #include "jsonconfig.h" #include #include + +#if (ARDUINO_LOG_LEVEL == 6) +#include +#endif + #include #include #include @@ -22,7 +26,8 @@ #include #include -#define GSCRIPTS_DELAY (10 * 60 * 1000) // 10 minute delay between pushes to Google Sheets directly +#define GSCRIPTS_DELAY (10000) // DEBUG: TODO: I cranked this up for testing. +// #define GSCRIPTS_DELAY (10 * 60 * 1000) // 10 minute delay between pushes to Google Sheets directly #define BREWERS_FRIEND_DELAY (15 * 60 * 1000) // 15 minute delay between pushes to Brewer's Friend #define BREWFATHER_DELAY (15 * 60 * 1000) // 15 minute delay between pushes to Brewfather @@ -33,6 +38,8 @@ #define GSCRIPTS_MIN_URL_LENGTH 24 #define GSCRIPTS_MIN_EMAIL_LENGTH 7 +#define GSHEETS_JSON 512 + // This is me being simplifying the reuse of code. The formats for Brewers Friend and Brewfather are basically the same // so I'm combining them together in one function #define BF_MEANS_BREWFATHER 1 @@ -60,9 +67,6 @@ class dataSendHandler uint64_t send_checkin_at; #endif - void setClock(); - static bool send_to_url_https(const char *url, const char *apiKey, const char *dataToSend, const char *contentType); - bool send_to_localTarget(); bool send_to_brewstatus(); bool send_to_google(); diff --git a/src/serialhandler.cpp b/src/serialhandler.cpp index e3b0b207..89d31859 100644 --- a/src/serialhandler.cpp +++ b/src/serialhandler.cpp @@ -34,6 +34,7 @@ void printTimestamp(Print *_logOutput) char c[12]; sprintf(c, "%10lu ", millis()); _logOutput->print(c); + Serial.flush(); } size_t printDot() @@ -43,7 +44,7 @@ size_t printDot() size_t printDot(bool safe) { -#ifndef ARDUINO_LOG_LEVEL +#ifdef ARDUINO_LOG_LEVEL return SERIAL.print(F(".")); #else return 0; @@ -57,7 +58,7 @@ size_t printChar(const char *chr) size_t printChar(bool safe, const char *chr) { -#ifndef ARDUINO_LOG_LEVEL +#ifdef ARDUINO_LOG_LEVEL return SERIAL.println(chr); #else return 0; @@ -71,7 +72,7 @@ size_t printCR() size_t printCR(bool safe) { -#ifndef ARDUINO_LOG_LEVEL +#ifdef ARDUINO_LOG_LEVEL return SERIAL.println(); #else return 0; diff --git a/src/tilt/tiltScanner.cpp b/src/tilt/tiltScanner.cpp index 0906013d..cf3531a9 100644 --- a/src/tilt/tiltScanner.cpp +++ b/src/tilt/tiltScanner.cpp @@ -54,16 +54,14 @@ void tiltScanner::init() pBLEScan->setActiveScan(false); pBLEScan->setInterval(97); // Select prime numbers to reduce risk of frequency beat pattern with ibeacon advertisement interval pBLEScan->setWindow(37); // Set to less or equal setInterval value. Leave reasonable gap to allow WiFi some time. + + tilt_scanner.scan(); } void tiltScanner::deinit() { - // pBLEScan->stop(); wait_until_scan_complete(); - // NimBLE fails to reinitialize after a call to deinit() (but thankfully it's light enough weight that we don't - // have to call deinit to use https any longer) - // https://github.com/h2zero/NimBLE-Arduino/issues/23 - // NimBLEDevice::deinit(); // Deinitialize the scanner & release memory + NimBLEDevice::deinit(); // Deinitialize the scanner & release memory } bool tiltScanner::scan() diff --git a/src/wifi_setup.cpp b/src/wifi_setup.cpp index 010ac730..f533ee52 100644 --- a/src/wifi_setup.cpp +++ b/src/wifi_setup.cpp @@ -14,19 +14,16 @@ bool shouldSaveConfig = false; uint64_t wifi_reset_pressed_at = 0; -//callback notifying us of the need to save config -// TODO - This can probably be eliminated -void saveConfigCallback() +void saveParamsCallback() { - // Serial.println("Should save config"); + // Callback notifying us of the need to save config shouldSaveConfig = true; } -// callback to display the WiFi LCD notification -void configModeCallback(AsyncWiFiManager *myWiFiManager) +void apCallback(WiFiManager *myWiFiManager) { + // Callback to display the WiFi LCD notification Log.verbose(F("Entered config mode: SSID: %s, IP: %s" CR), myWiFiManager->getConfigPortalSSID().c_str(), WiFi.softAPIP().toString().c_str()); - // Assuming WIFI_SETUP_AP_PASS here. lcd.display_wifi_connect_screen(myWiFiManager->getConfigPortalSSID().c_str(), WIFI_SETUP_AP_PASS); } @@ -43,74 +40,50 @@ void disconnect_from_wifi_and_restart() void mdnsreset() { tilt_scanner.wait_until_scan_complete(); // Wait for scans to complete (we don't want any tasks running in the background) - MDNS.end(); - WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); - WiFi.setHostname(config.mdnsID); - WiFi.begin(); - while (WiFi.status() != WL_CONNECTED) - { - delay(50); - yield(); - } - if (!MDNS.begin(config.mdnsID)) - { - Log.error(F("Error resetting MDNS responder, rebooting.")); - ESP.restart(); - } - else - { - Log.notice(F("mDNS responder restarted, hostname: %s.local." CR), WiFi.getHostname()); - MDNS.addService("http", "tcp", WEBPORT); - MDNS.addService("tiltbridge", "tcp", WEBPORT); -#if DOTELNET == true - MDNS.addService("telnet", "tcp", TELNETPORT); -#endif - } http_server.name_reset_requested = false; + ESP.restart(); // TODO: This no longer works as designed } void init_wifi() { - AsyncWiFiManager wifiManager; //Local initialization. Once its business is done, there is no need to keep it around + WiFiManager wm; #if ARDUINO_LOG_LEVEL == 6 - wifiManager.setDebugOutput(true); // Use debug if we are at mad log level + wm.setDebugOutput(true); // Use debug if we are at max log level #else - wifiManager.setDebugOutput(false); // In case we have a serial connection + wm.setDebugOutput(false); // In case we have a serial connection #endif // The main purpose of this is to set a boolean value which will allow us to know we // just saved a new configuration (as opposed to rebooting normally) - wifiManager.setSaveConfigCallback(saveConfigCallback); - wifiManager.setAPCallback(configModeCallback); - wifiManager.setConfigPortalTimeout(5 * 60); // Setting to 5 mins + wm.setSaveParamsCallback(saveParamsCallback); + wm.setAPCallback(apCallback); + wm.setConfigPortalTimeout(5 * 60); // Setting to 5 mins - wifiManager.setCleanConnect(true); - wifiManager.setCustomHeadElement(""); + wm.setCleanConnect(true); + wm.setCustomHeadElement(""); // The third parameter we're passing here (mdns_id.c_str()) is the default name that will appear on the form. // It's nice, but it means the user gets no actual prompt for what they're entering. char mdns_id[31]; strcpy(mdns_id, config.mdnsID); - AsyncWiFiManagerParameter custom_mdns_name("mdns", "Device (mDNS) Name", mdns_id, 20); - wifiManager.addParameter(&custom_mdns_name); + WiFiManagerParameter custom_mdns_name("mdns", "Device (mDNS) Name", mdns_id, 20); + wm.addParameter(&custom_mdns_name); - if (wifiManager.autoConnect(WIFI_SETUP_AP_NAME, WIFI_SETUP_AP_PASS)) + if (!wm.autoConnect(WIFI_SETUP_AP_NAME, WIFI_SETUP_AP_PASS)) { - // TODO - Determine if we can merge shouldSaveConfig in here - WiFi.softAPdisconnect(true); - WiFi.mode(WIFI_STA); + Log.warning(F("Failed to connect and/or hit timeout. Restarting" CR)); + ESP.restart(); } else { - // If we haven't successfully connected to WiFi, just restart & continue to project the configuration AP. - // Alternatively, we can hang here. - ESP.restart(); + // We finished with portal (We were configured) + // WiFi.softAPdisconnect(true); + // WiFi.mode(WIFI_STA); } - // Alright. We're theoretically connected here. - // If we connected, then let's save the mDNS name if (shouldSaveConfig) { + // If we connected, then let's save the mDNS name LCBUrl url; // If the mDNS name is valid, save it. if (url.isValidHostName(custom_mdns_name.getValue())) @@ -120,15 +93,16 @@ void init_wifi() } else { - // If the mDNS name is invalid, reset the WiFi configuration and restart the ESP8266 + // If the mDNS name is invalid, reset the WiFi configuration and restart + // the ESP8266 // TODO - add an LCD error message here maybe disconnect_from_wifi_and_restart(); } - saveConfig(); } + WiFi.setHostname(config.mdnsID); - if (!MDNS.begin(mdns_id)) + if (!MDNS.begin(config.mdnsID)) { Log.error(F("Error setting up MDNS responder." CR)); } @@ -151,14 +125,6 @@ void init_wifi() strcat(ip_address_url, "/"); lcd.display_wifi_success_screen(mdns_url, ip_address_url); - - // In order to have the system register the mDNS name in DHCP table, it is necessary to flush config - // and reinitialize Wifi connection. If this is not done, the DHCP hostname is always just registered - // as espressif. See: https://github.com/espressif/arduino-esp32/issues/2537 - WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); - WiFi.setHostname(mdns_id); - WiFi.begin(); - delay(1000); } #ifndef LCD_TFT diff --git a/src/wifi_setup.h b/src/wifi_setup.h index 8d32bb73..2635cb63 100644 --- a/src/wifi_setup.h +++ b/src/wifi_setup.h @@ -9,7 +9,7 @@ #include "tiltBridge.h" #include "jsonconfig.h" #include -#include +#include #include #include #include