diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fc0ebae..5bcac4f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - no_std_strings # tags: # - "v*" # pull_request: @@ -21,11 +22,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v4.1.1 - run: git fetch --prune --unshallow - name: Cache pip - uses: actions/cache@v3.2.2 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} @@ -33,13 +34,13 @@ jobs: ${{ runner.os }}-pip- - name: Cache PlatformIO - uses: actions/cache@v3.2.2 + uses: actions/cache@v4 with: path: ~/.platformio key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} - name: Set up Python - uses: actions/setup-python@v4.4.0 + uses: actions/setup-python@v5 - name: Install PlatformIO run: | @@ -82,4 +83,4 @@ jobs: LICENSE bin/*.bin env: - GITHUB_REPOSITORY: thorrak/tiltbridge \ No newline at end of file + GITHUB_REPOSITORY: thorrak/tiltbridge diff --git a/docs/source/hardware.rst b/docs/source/hardware.rst index 354dc06..b586a89 100644 --- a/docs/source/hardware.rst +++ b/docs/source/hardware.rst @@ -82,6 +82,7 @@ OLED Cases - `TiltBridge Heltec/TTGO ESP32 OLED Enclosure by Thorrak `_ - `Generic "OLED" Enclosure by Thorrak `_ - `HiLetGo Wifi 32 Tiltbridge Case by calandryll `_ +- `Ideaspark OLED 0.96" ESP32 case `_ diff --git a/platformio.ini b/platformio.ini index df75bb2..9a8933d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -27,7 +27,7 @@ monitor_rts = 1 build_flags = ; Do not use spaces around the "=" here, will give you a builder not found error !python tools/git_rev.py ; Pick up git information for version (disabled), branch, and commit (in version.cpp) - -D PIO_SRC_TAG=1.2.1 ; Increment versions shown in about.htm page (from version.cpp) + -D PIO_SRC_TAG=1.2.2 ; Increment versions shown in about.htm page (from version.cpp) ; Async TCP Settings: -D CONFIG_ASYNC_TCP_RUNNING_CORE=1 ; per: https://github.com/me-no-dev/ESPAsyncWebServer/issues/731#issuecomment-628163515 -D CONFIG_ASYNC_TCP_USE_WDT=1 @@ -40,6 +40,7 @@ build_flags = ; Do not use spaces around the "=" here, -D PRINT_GRAV_UPDATES=0 ; Turn on and off gravity printing to serial log -D CORE_DEBUG_LEVEL=0 ; Set core Arduino log level (5 = high) -D ARDUINO_LOG_LEVEL=0 ; Set Serial log level (6 = high) + -D CONFIG_NIMBLE_CPP_LOG_LEVEL=0 ; Set NimBLE log level (4 = high) ;-D PARSE_DEBUG ; Turn main Parse debug logging on ;-D PARSE_DEBUG_RESPONSE ; Turn Parse response debugging on -D DISABLE_LOGGING ; This will remove log lib from sketch @@ -53,15 +54,15 @@ build_flags = ; Do not use spaces around the "=" here, ;-D FSEDIT=1 ; Use a filesystem editor extra_scripts = tools/get_port.py ; Pick up port information based on OS lib_deps = - bblanchon/ArduinoJson @ 6.21.2 ; https://github.com/bblanchon/ArduinoJson + bblanchon/ArduinoJson @ 6.21.5 ; https://github.com/bblanchon/ArduinoJson ; thijse/ArduinoLog @ 1.1.1 ; https://github.com/thijse/Arduino-Log.git https://github.com/thorrak/Arduino-Log.git ; // Need this until ArduinoLog merges https://github.com/thijse/Arduino-Log/pull/23 https://github.com/lbussy/esptelnet.git https://github.com/me-no-dev/ESPAsyncWebServer.git https://github.com/thorrak/WiFiManager.git#feature_asyncwebserver h2zero/NimBLE-Arduino @ 1.4.1 ; https://github.com/h2zero/NimBLE-Arduino - 256dpi/MQTT @ 2.5.1 ; https://github.com/256dpi/arduino-mqtt - lbussy/LCBUrl @ ^1.1.7 + 256dpi/MQTT @ 2.5.2 ; https://github.com/256dpi/arduino-mqtt + lbussy/LCBUrl @ ^1.1.9 https://github.com/lbussy/Parse-SDK-Arduino.git#fix_warnings espi_lib_deps = bodmer/TFT_eSPI @ 2.4.79 ; https://github.com/Bodmer/TFT_eSPI.git diff --git a/src/OTAUpdate.h b/src/OTAUpdate.h index 743cbbb..9125a18 100644 --- a/src/OTAUpdate.h +++ b/src/OTAUpdate.h @@ -1,9 +1,6 @@ #ifndef TILTBRIDGE_OTAUPDATE_H #define TILTBRIDGE_OTAUPDATE_H -#include -#include - // Although this should be automatically done with build flags, OTA updates // are explicitly not supported on the "legacy" OLED screen version due to // flash constraints. TFT + 16MB flash only! diff --git a/src/bridge_lcd.cpp b/src/bridge_lcd.cpp index ba53876..5a3d51d 100644 --- a/src/bridge_lcd.cpp +++ b/src/bridge_lcd.cpp @@ -1,13 +1,14 @@ -#include "bridge_lcd.h" -#include "jsonconfig.h" -#include "tilt/tiltScanner.h" -#include #include +#include #ifdef LCD_SSD1306 #include #endif +#include "jsonconfig.h" +#include "tilt/tiltScanner.h" +#include "bridge_lcd.h" + bridge_lcd lcd; #if defined(LCD_SSD1306) || defined(LCD_TFT_ESPI) @@ -498,9 +499,10 @@ void bridge_lcd::display_wifi_reconnect_failed() { } void bridge_lcd::print_tilt_to_line(tiltHydrometer *tilt, uint8_t line) { - char gravity[11], temp[8]; - sprintf(gravity, "%s", tilt->converted_gravity(false).c_str()); - sprintf(temp, "%s %s", tilt->converted_temp(false).c_str(), tilt->is_celsius() ? "C" : "F"); + char gravity[11], temp[9], temp_str[6]; + tilt->converted_gravity(gravity, 11, false); + tilt->converted_temp(temp_str, 6, false); + snprintf(temp, sizeof(temp), "%s %s", temp_str, tilt->is_celsius() ? "C" : "F"); #if defined(LCD_TFT_ESPI) tft->setTextColor(tilt_text_colors[tilt->m_color]); diff --git a/src/getGuid.cpp b/src/getGuid.cpp index 4d1415e..5207c9c 100644 --- a/src/getGuid.cpp +++ b/src/getGuid.cpp @@ -1,4 +1,5 @@ #include + #include "getGuid.h" void getGuid(char *str) diff --git a/src/getGuid.h b/src/getGuid.h index eed68a1..a1c78b9 100644 --- a/src/getGuid.h +++ b/src/getGuid.h @@ -1,8 +1,6 @@ #ifndef _GET_GUID_H #define _GET_GUID_H -#include - void getGuid(char *str); #endif // _GET_GUID_H diff --git a/src/http_server.cpp b/src/http_server.cpp index d57ffed..5cd891e 100644 --- a/src/http_server.cpp +++ b/src/http_server.cpp @@ -1,33 +1,22 @@ #include -#include -#include -#include -#include #include -#include +#include +#include #include "resetreasons.h" #include "uptime.h" #include "version.h" -#include "http_server.h" #include "jsonconfig.h" #include "tilt/tiltScanner.h" +#include "sendData.h" + +#include "http_server.h" httpServer http_server; Ticker sendNowTicker; -extern bool send_cloudTarget; -extern bool send_brewersFriend; -extern bool send_brewfather; -extern bool send_userTarget; -extern bool send_grainfather; -extern bool send_localTarget; -extern bool send_brewStatus; -extern bool send_gSheets; -extern bool send_mqtt; - AsyncWebServer server(WEBPORT); // This is to simplify the redirects in processCalibration @@ -220,7 +209,7 @@ bool processCloudTargetSettings(AsyncWebServerRequest *request) { if (!config.cloudEnabled) { config.cloudEnabled = true; // Trigger a send to Cloud in 5 seconds - sendNowTicker.once(5, [](){send_cloudTarget = true;}); + sendNowTicker.once(5, [](){data_sender.send_cloudTarget = true;}); } Log.notice(F("Settings update, [%s]:(%s) applied.\r\n"), name, value); } else if (strcmp(value, "false") == 0) { @@ -269,7 +258,7 @@ bool processLocalTargetSettings(AsyncWebServerRequest *request) { Log.notice(F("Settings update, [%s]:(%s) applied.\r\n"), name, value); strlcpy(config.localTargetURL, value, 256); // Trigger a send to Fermentrack/BPR in 5 seconds using the updated URL - sendNowTicker.once(5, [](){send_localTarget = true;}); + sendNowTicker.once(5, [](){data_sender.send_localTarget = true;}); } else if (strcmp(value, "") == 0 || strlen(value) == 0) { Log.notice(F("Settings update, [%s]:(%s) cleared.\r\n"), name, value); strlcpy(config.localTargetURL, value, 256); @@ -324,7 +313,7 @@ bool processGoogleSheetsSettings(AsyncWebServerRequest *request) { strlcpy(config.scriptsURL, value, 256); Log.notice(F("Settings update, [%s]:(%s) applied.\r\n"), name, value); // Trigger a send in 5 seconds using the updated GSheets URL - sendNowTicker.once(5, [](){send_gSheets = true;}); + sendNowTicker.once(5, [](){data_sender.send_gSheets = true;}); } else if (strcmp(value, "") == 0 || strlen(value) == 0) { strlcpy(config.scriptsURL, value, 256); Log.notice(F("Settings update, [%s]:(%s) cleared.\r\n"), name, value); @@ -412,7 +401,7 @@ bool processBrewersFriendSettings(AsyncWebServerRequest *request) { strlcpy(config.brewersFriendKey, value, 65); Log.notice(F("Settings update, [%s]:(%s) applied.\r\n"), name, value); // Trigger a send to Brewers Friend in 5 seconds using the updated key - sendNowTicker.once(5, [](){send_brewersFriend = true;}); + sendNowTicker.once(5, [](){data_sender.send_brewersFriend = true;}); } else if (strcmp(value, "") == 0 || strlen(value) == 0) { strlcpy(config.brewersFriendKey, value, 65); Log.notice(F("Settings update, [%s]:(%s) cleared.\r\n"), name, value); @@ -457,7 +446,7 @@ bool processBrewfatherSettings(AsyncWebServerRequest *request) strlcpy(config.brewfatherKey, value, 65); Log.notice(F("Settings update, [%s]:(%s) applied.\r\n"), name, value); // Trigger a send to Brewfather in 5 seconds using the updated key - sendNowTicker.once(5, [](){send_brewfather = true;}); + sendNowTicker.once(5, [](){data_sender.send_brewfather = true;}); } else if (strcmp(value, "") == 0 || strlen(value) == 0) { strlcpy(config.brewfatherKey, value, 65); Log.notice(F("Settings update, [%s]:(%s) cleared.\r\n"), name, value); @@ -502,7 +491,7 @@ bool processUserTargetSettings(AsyncWebServerRequest *request) strlcpy(config.userTargetURL, value, 65); Log.notice(F("Settings update, [%s]:(%s) applied.\r\n"), name, value); // Trigger a send to the user target in 5 seconds using the updated key - sendNowTicker.once(5, [](){send_userTarget = true;}); + sendNowTicker.once(5, [](){data_sender.send_userTarget = true;}); } else if (strcmp(value, "") == 0 || strlen(value) == 0) { strlcpy(config.userTargetURL, value, 128); Log.notice(F("Settings update, [%s]:(%s) cleared.\r\n"), name, value); @@ -557,7 +546,7 @@ bool processGrainfatherSettings(AsyncWebServerRequest *request) if (GRAINFATHER_MIN_URL_LENGTH < strlen(value) && strlen(value) < 64) { strlcpy(config.grainfatherURL[to_color].link, value, 64); Log.notice(F("Settings update, [%s]:(%s) applied.\r\n"), name, value); - sendNowTicker.once(5, [](){send_grainfather = true;}); + sendNowTicker.once(5, [](){data_sender.send_grainfather = true;}); } else if (strcmp(value, "") == 0 || strlen(value) == 0) { strlcpy(config.grainfatherURL[to_color].link, value, 64); Log.notice(F("Settings update, [%s]:(%s) cleared.\r\n"), name, value); @@ -601,7 +590,7 @@ bool processBrewstatusSettings(AsyncWebServerRequest *request) { strlcpy(config.brewstatusURL, value, 256); Log.notice(F("Settings update, [%s]:(%s) applied.\r\n"), name, value); // Trigger a send to Brewstatus in 5 seconds using the updated key - sendNowTicker.once(5, [](){send_brewStatus = true;}); + sendNowTicker.once(5, [](){data_sender.send_brewStatus = true;}); } else if (strcmp(value, "") == 0 || strlen(value) == 0) { strlcpy(config.brewstatusURL, value, 256); Log.notice(F("Settings update, [%s]:(%s) cleared.\r\n"), name, value); @@ -794,7 +783,7 @@ bool processMqttSettings(AsyncWebServerRequest *request) { } else { if (config.save()) { // Trigger a send via MQTT in 5 seconds using the updated data - sendNowTicker.once(5, [](){send_mqtt = true;}); + sendNowTicker.once(5, [](){data_sender.send_mqtt = true;}); return true; } else { Log.error(F("Error: Unable to save MQTT configuration data.\r\n")); diff --git a/src/http_server.h b/src/http_server.h index 8c9c67e..8a379fe 100644 --- a/src/http_server.h +++ b/src/http_server.h @@ -1,12 +1,6 @@ #ifndef TILTBRIDGE_HTTP_SERVER_H #define TILTBRIDGE_HTTP_SERVER_H -#include "wifi_setup.h" -#include "OTAUpdate.h" -#include "sendData.h" -#include "jsonconfig.h" -#include "parseTarget.h" - #ifdef FSEDIT #include #endif diff --git a/src/jsonconfig.cpp b/src/jsonconfig.cpp index 25758e5..7465ffe 100644 --- a/src/jsonconfig.cpp +++ b/src/jsonconfig.cpp @@ -6,13 +6,10 @@ #include #endif - -#include "main.h" +#include "getGuid.h" #include "serialhandler.h" - #include "jsonconfig.h" -#include "bridge_lcd.h" #define MAX_FILENAME_LENGTH 32 diff --git a/src/main.cpp b/src/main.cpp index 0fd85a8..3beba85 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,9 +2,20 @@ // Please note - This source code (along with other files) are provided under license. // More details (including license details) can be found in the files accompanying this source code. +#include -#include "main.h" +#include "watchButtons.h" #include "tilt/tiltScanner.h" +#include "http_server.h" +#include "wifi_setup.h" +#include "sendData.h" +#include "parseTarget.h" +#include "jsonconfig.h" +#include "bridge_lcd.h" +#include "serialhandler.h" +#include "main.h" + + #if (ARDUINO_LOG_LEVEL >= 5) Ticker memCheck; @@ -16,7 +27,7 @@ void printMem() { const uint32_t free = ESP.getFreeHeap(); const uint32_t max = ESP.getMaxAllocHeap(); const uint8_t frag = 100 - (max * 100) / free; - Log.verbose(F("Free Heap: %d, Largest contiguous block: %d, Frag: %d%%\r\n"), free, max, frag); + Log.info(F("Free Heap: %d, Largest contiguous block: %d, Frag: %d%%\r\n"), free, max, frag); } void reboot() @@ -52,7 +63,8 @@ void setup() { initButtons(); // Initialize buttons // Start independent timers -#if (ARDUINO_LOG_LEVEL >= 5) && !defined(DISABLE_LOGGING) + // ARDUINO_LOG_LOG_LEVEL_INFO is 4 +#if (ARDUINO_LOG_LEVEL >= ARDUINO_LOG_LOG_LEVEL_INFO) && !defined(DISABLE_LOGGING) memCheck.attach(30, printMem); // Memory debug print on timer #endif @@ -68,14 +80,7 @@ void loop() { serialLoop(); // Service telnet and console commands checkButtons(); // Check for reset calls - send_to_cloud(); - data_sender.send_to_localTarget(); - send_to_bf_and_bf(); - data_sender.send_to_brewstatus(); - data_sender.send_to_grainfather(); - data_sender.send_to_taplistio(); - data_sender.send_to_google(); - data_sender.send_to_mqtt(); + data_sender.process(); if (tilt_scanner.scan()) { // The scans are done asynchronously, so we'll poke the scanner to see if diff --git a/src/main.h b/src/main.h index a614c56..1ec4c85 100644 --- a/src/main.h +++ b/src/main.h @@ -1,21 +1,6 @@ #ifndef _MAIN_H #define _MAIN_H -#include "watchButtons.h" -#include "http_server.h" -#include "sendData.h" -#include "bridge_lcd.h" -#include "wifi_setup.h" -#include "serialhandler.h" -#include "jsonconfig.h" -#include "parseTarget.h" -#include -#include -#include - -#ifdef LOG_LOCAL_LEVEL -#include -#endif void printMem(); void setup(); diff --git a/src/parseTarget.cpp b/src/parseTarget.cpp index 280f9a1..5a26054 100644 --- a/src/parseTarget.cpp +++ b/src/parseTarget.cpp @@ -1,4 +1,12 @@ +#include +#include +#include + #include "parseTarget.h" + +#include "jsonconfig.h" +#include "version.h" +#include "sendData.h" #include "tilt/tiltScanner.h" #define SERVER_URL "http://tiltbridge.com/cloudkeys/keys.json" @@ -6,8 +14,6 @@ static bool parseHasKeys = false; static bool parseIsSetup = false; -extern bool send_cloudTarget; - void doParsePoll() // Get Parse data from git repo { if (!parseHasKeys) @@ -177,6 +183,6 @@ void addTiltToParse() // Dispatch data to Parse else { doParseSetup(); - send_cloudTarget = true; + data_sender.send_cloudTarget = true; } } diff --git a/src/parseTarget.h b/src/parseTarget.h index 0a25995..ecd8ae8 100644 --- a/src/parseTarget.h +++ b/src/parseTarget.h @@ -1,17 +1,6 @@ #ifndef _PARSE_TARGET_H #define _PARSE_TARGET_H -#include "version.h" -#include "getGuid.h" -#include "jsonconfig.h" -#include "tilt/tiltHydrometer.h" -#include -#include -#include -#include -#include -#include - void doParsePoll(); void doParseSetup(); void addTiltToParse(); diff --git a/src/sendData.cpp b/src/sendData.cpp index 04c2b09..471c9cc 100644 --- a/src/sendData.cpp +++ b/src/sendData.cpp @@ -1,36 +1,30 @@ -#include "sendData.h" +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + #include "tilt/tiltScanner.h" +#include "jsonconfig.h" +#include "version.h" +#include "parseTarget.h" +#include "http_server.h" +#include "main.h" // for printMem() + +#include "sendData.h" dataSendHandler data_sender; // Global data sender MQTTClient mqttClient(512); -// POST Timers -Ticker cloudTargetTicker; -Ticker localTargetTicker; -Ticker brewersFriendTicker; -Ticker brewfatherTicker; -Ticker userTargetTicker; -Ticker grainfatherTicker; -Ticker brewStatusTicker; -//Ticker taplistioTicker; // Now inside dataSendHandler object -Ticker gSheetsTicker; -Ticker mqttTicker; - -// POST Semaphores -bool send_cloudTarget = false; -bool send_localTarget = false; -bool send_brewersFriend = false; -bool send_brewfather = false; -bool send_userTarget = false; -bool send_grainfather = false; -bool send_brewStatus = false; -//bool send_taplistio = false; // Now inside dataSendHandler object -bool send_gSheets = false; -bool send_mqtt = false; -bool send_lock = false; - dataSendHandler::dataSendHandler() {} void dataSendHandler::init() @@ -38,34 +32,45 @@ void dataSendHandler::init() init_mqtt(); // Set up timers - // DEBUG: - cloudTargetTicker.once(10, [](){send_cloudTarget = true;}); // Schedule first send to Cloud Target - localTargetTicker.once(20, [](){send_localTarget = true;}); // Schedule first send to Local Target -// localTargetTicker.once(5, [](){send_localTarget = true;}); // Schedule first send to Local Target - // DEBUG^ - brewStatusTicker.once(30, [](){send_brewStatus = true;}); // Schedule first send to Brew Status - brewfatherTicker.once(40, [](){send_brewfather = true;}); // Schedule first send to Brewfather - brewersFriendTicker.once(50, [](){send_brewersFriend = true;}); // Schedule first send to Brewer's Friend - userTargetTicker.once(60, [](){send_userTarget = true;}); // Schedule first send to User-defined JSON target - mqttTicker.once(65, [](){send_mqtt = true;}); // Schedule first send to MQTT - gSheetsTicker.once(70, [](){send_gSheets = true;}); // Schedule first send to Google Sheets - grainfatherTicker.once(80, [](){send_grainfather = true;}); // Schedule first send to Grainfather +// localTargetTicker.once(5, [](){data_sender.send_localTarget = true;}); // Schedule first send to Local Target + localTargetTicker.once(10, [](){data_sender.send_localTarget = true;}); // Schedule first send to Local Target + mqttTicker.once(20, [](){data_sender.send_mqtt = true;}); // Schedule first send to MQTT + brewStatusTicker.once(30, [](){data_sender.send_brewStatus = true;}); // Schedule first send to Brew Status + brewfatherTicker.once(40, [](){data_sender.send_brewfather = true;}); // Schedule first send to Brewfather + brewersFriendTicker.once(50, [](){data_sender.send_brewersFriend = true;}); // Schedule first send to Brewer's Friend + userTargetTicker.once(60, [](){data_sender.send_userTarget = true;}); // Schedule first send to User-defined JSON target + gSheetsTicker.once(70, [](){data_sender.send_gSheets = true;}); // Schedule first send to Google Sheets + grainfatherTicker.once(80, [](){data_sender.send_grainfather = true;}); // Schedule first send to Grainfather taplistioTicker.once(90, [](){data_sender.send_taplistio = true;}); // Schedule first send to Taplist.io + cloudTargetTicker.once(100, [](){data_sender.send_cloudTarget = true;}); // Schedule first send to Cloud Target +} + +void dataSendHandler::process() +{ + if (WiFiClass::status() == WL_CONNECTED) { + send_to_cloud(); + send_to_localTarget(); + send_to_bf_and_bf(); + send_to_grainfather(); + send_to_brewstatus(); + send_to_taplistio(); + send_to_google(); + send_to_mqtt(); + } } bool dataSendHandler::send_to_localTarget() { bool result = true; - if (send_localTarget && ! send_lock) + if (data_sender.send_localTarget && !send_lock) { // Local Target send_localTarget = false; send_lock = true; // tilt_scanner.deinit(); - if (WiFiClass::status() == WL_CONNECTED && strlen(config.localTargetURL) >= LOCALTARGET_MIN_URL_LENGTH) - { + if (strlen(config.localTargetURL) >= LOCALTARGET_MIN_URL_LENGTH) { Log.verbose(F("Calling send to Local Target.\r\n")); DynamicJsonDocument doc(TILT_ALL_DATA_SIZE + 128); char tilt_data[TILT_ALL_DATA_SIZE + 128]; @@ -77,7 +82,7 @@ bool dataSendHandler::send_to_localTarget() serializeJson(doc, tilt_data); - if (send_to_url(config.localTargetURL, "", tilt_data, "application/json")) + if (send_to_url(config.localTargetURL, tilt_data, content_json)) { Log.notice(F("Completed send to Local Target.\r\n")); } @@ -87,23 +92,22 @@ bool dataSendHandler::send_to_localTarget() Log.verbose(F("Error sending to Local Target.\r\n")); } } - localTargetTicker.once(config.localTargetPushEvery, [](){send_localTarget = true;}); // Set up subsequent send to localTarget + localTargetTicker.once(config.localTargetPushEvery, [](){data_sender.send_localTarget = true;}); // Set up subsequent send to localTarget // tilt_scanner.init(); send_lock = false; } return result; } -bool send_to_bf_and_bf() +bool dataSendHandler::send_to_bf_and_bf() { bool retval = false; - if (send_brewersFriend && ! send_lock) + if (data_sender.send_brewersFriend && !send_lock) { send_lock = true; // Brewer's Friend - send_brewersFriend = false; - if (WiFiClass::status() == WL_CONNECTED && strlen(config.brewersFriendKey) > BREWERS_FRIEND_MIN_KEY_LENGTH) - { + data_sender.send_brewersFriend = false; + if (strlen(config.brewersFriendKey) > BREWERS_FRIEND_MIN_KEY_LENGTH) { Log.verbose(F("Calling send to Brewer's Friend.\r\n")); retval = data_sender.send_to_bf_and_bf(BF_MEANS_BREWERS_FRIEND); if (retval) @@ -115,17 +119,16 @@ bool send_to_bf_and_bf() Log.verbose(F("Error sending to Brewer's Friend.\r\n")); } } - brewersFriendTicker.once(BREWERS_FRIEND_DELAY, [](){send_brewersFriend = true;}); // Set up subsequent send to Brewer's Friend + brewersFriendTicker.once(BREWERS_FRIEND_DELAY, [](){data_sender.send_brewersFriend = true;}); // Set up subsequent send to Brewer's Friend send_lock = false; } - if (send_brewfather && ! send_lock) + if (data_sender.send_brewfather && !send_lock) { send_lock = true; // Brewfather - send_brewfather = false; - if (WiFiClass::status() == WL_CONNECTED && strlen(config.brewfatherKey) > BREWFATHER_MIN_KEY_LENGTH) - { + data_sender.send_brewfather = false; + if (strlen(config.brewfatherKey) > BREWFATHER_MIN_KEY_LENGTH) { Log.verbose(F("Calling send to Brewfather.\r\n")); retval = data_sender.send_to_bf_and_bf(BF_MEANS_BREWFATHER); if (retval) @@ -137,17 +140,17 @@ bool send_to_bf_and_bf() Log.verbose(F("Error sending to Brewfather.\r\n")); } } - brewfatherTicker.once(BREWFATHER_DELAY, [](){send_brewfather = true;}); // Set up subsequent send to Brewfather + brewfatherTicker.once(BREWFATHER_DELAY, [](){data_sender.send_brewfather = true;}); // Set up subsequent send to Brewfather send_lock = false; } - if (send_userTarget && ! send_lock) + if (data_sender.send_userTarget && !send_lock) { send_lock = true; // User Target - send_userTarget = false; - if (WiFiClass::status() == WL_CONNECTED && strlen(config.userTargetURL) > USER_TARGET_MIN_URL_LENGTH) + data_sender.send_userTarget = false; + if (strlen(config.userTargetURL) > USER_TARGET_MIN_URL_LENGTH) { Log.verbose(F("Calling send to User Target.\r\n")); retval = data_sender.send_to_bf_and_bf(BF_MEANS_USER_TARGET); @@ -160,19 +163,19 @@ bool send_to_bf_and_bf() Log.verbose(F("Error sending to User Target.\r\n")); } } - userTargetTicker.once(USER_TARGET_DELAY, [](){send_userTarget = true;}); // Set up subsequent send to User Target + userTargetTicker.once(USER_TARGET_DELAY, [](){data_sender.send_userTarget = true;}); // Set up subsequent send to User Target send_lock = false; } return retval; } -void send_to_cloud() +void dataSendHandler::send_to_cloud() { - if (send_cloudTarget && ! send_lock) { + if (data_sender.send_cloudTarget && !send_lock) { send_lock = true; send_cloudTarget = false; addTiltToParse(); - cloudTargetTicker.once(CLOUD_DELAY, [](){send_cloudTarget = true;}); // Set up subsequent send to localTarget + cloudTargetTicker.once(CLOUD_DELAY, [](){data_sender.send_cloudTarget = true;}); // Set up subsequent send to localTarget } send_lock = false; } @@ -230,18 +233,23 @@ bool dataSendHandler::send_to_bf_and_bf(const uint8_t which_bf) { if (tilt_scanner.tilt(i)->is_loaded()) { + char gravity[10]; + char temp[6]; + Log.verbose(F("Tilt loaded with color name: %s\r\n"), tilt_color_names[i]); j["name"] = tilt_color_names[i]; - j["temp"] = tilt_scanner.tilt(i)->converted_temp(true); // Always in Fahrenheit + tilt_scanner.tilt(i)->converted_temp(temp, sizeof(temp), true); // Always in Fahrenheit + j["temp"] = temp; j["temp_unit"] = "F"; - j["gravity"] = tilt_scanner.tilt(i)->converted_gravity(false); + tilt_scanner.tilt(i)->converted_gravity(gravity, sizeof(gravity), false); + j["gravity"] = gravity; j["gravity_unit"] = "G"; j["device_source"] = "TiltBridge"; char payload_string[BF_SIZE]; serializeJson(j, payload_string); - if (!send_to_url(url, "", payload_string, "application/json")) + if (!send_to_url(url, payload_string, content_json)) result = false; // There was an error with the previous send } } @@ -251,48 +259,115 @@ bool dataSendHandler::send_to_bf_and_bf(const uint8_t which_bf) bool dataSendHandler::send_to_grainfather() { bool result = true; - StaticJsonDocument j; - if (send_grainfather && ! send_lock) + if (send_grainfather && !send_lock) { // Brew Status send_grainfather = false; send_lock = true; - if (WiFiClass::status() == WL_CONNECTED) + + Log.verbose(F("Calling send to Grainfather.\r\n")); + + // 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++) { - Log.verbose(F("Calling send to Grainfather.\r\n")); + if (strlen(config.grainfatherURL[i].link) == 0) { + continue; + } - // 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++) + if (tilt_scanner.tilt(i)->is_loaded()) { - if (strlen(config.grainfatherURL[i].link) == 0) { - continue; - } - - if (tilt_scanner.tilt(i)->is_loaded()) + char gravity[10]; + char temp[6]; + StaticJsonDocument j; + Log.verbose(F("Tilt loaded with color name: %s\r\n"), tilt_color_names[i]); + tilt_scanner.tilt(i)->converted_temp(temp, sizeof(temp), true); // Always in Fahrenheit + j["Temp"] = temp; + j["Unit"] = "F"; + tilt_scanner.tilt(i)->converted_gravity(gravity, sizeof(gravity), false); + j["SG"] = gravity; + + char payload_string[GF_SIZE]; + serializeJson(j, payload_string); + + if (!send_to_url(config.grainfatherURL[i].link, payload_string, content_json)) { - Log.verbose(F("Tilt loaded with color name: %s\r\n"), tilt_color_names[i]); - j["Temp"] = tilt_scanner.tilt(i)->converted_temp(true); // Always in Fahrenheit - j["Unit"] = "F"; - j["SG"] = tilt_scanner.tilt(i)->converted_gravity(false); - - char payload_string[GF_SIZE]; - serializeJson(j, payload_string); - - if (!http_send_json(config.grainfatherURL[i].link, payload_string)) - { - result = false; // There was an error with the previous send - } + result = false; // There was an error with the previous send } } } - grainfatherTicker.once(GRAINFATHER_DELAY, [](){send_grainfather = true;}); // Set up subsequent send to Grainfather + grainfatherTicker.once(GRAINFATHER_DELAY, [](){data_sender.send_grainfather = true;}); // Set up subsequent send to Grainfather send_lock = false; } return result; } +bool dataSendHandler::send_to_taplistio() +{ + bool result = true; + + // Check if config.taplistioURL is set, and return if it's not + if (strlen(config.taplistioURL) <= 10) { + return false; + } + + // See if it's our time to send. + if (!send_taplistio) { + return false; + } else if (send_lock) { + Log.verbose(F("taplist.io: send lock set.\r\n")); + return false; + } + + + // Since we're only using .once timers, we can just detach/recreate every time and be fine + taplistioTicker.detach(); + + // Attempt to send. + send_taplistio = false; + send_lock = true; + + // This is now checked in the data sending loop + // if (WiFiClass::status() != WL_CONNECTED) { + // Log.verbose(F("taplist.io: Wifi not connected, skipping send.\r\n")); + // taplistioTicker.once(config.taplistioPushEvery, [](){data_sender.send_taplistio = true;}); + // send_lock = false; + // return false; + // } + + for (uint8_t i = 0; i < TILT_COLORS; i++) { + StaticJsonDocument<192> j; + char payload_string[192]; + char gravity[10]; + char temp[6]; + + + if (!tilt_scanner.tilt(i)->is_loaded()) { + continue; + } + + j["Color"] = tilt_color_names[i]; + tilt_scanner.tilt(i)->converted_temp(temp, sizeof(temp), true); // Always in Fahrenheit + j["Temp"] = temp; + tilt_scanner.tilt(i)->converted_gravity(gravity, sizeof(gravity), false); + j["SG"] = gravity; + j["temperature_unit"] = "F"; + j["gravity_unit"] = "G"; + + serializeJson(j, payload_string); + + Log.verbose(F("taplist.io: Sending %s Tilt to %s\r\n"), tilt_color_names[i], config.taplistioURL); + + result = send_to_url(config.taplistioURL, payload_string, content_json); + } + + taplistioTicker.once(config.taplistioPushEvery, [](){data_sender.send_taplistio = true;}); + send_lock = false; + return result; +} + + bool dataSendHandler::send_to_brewstatus() { bool result = true; @@ -304,8 +379,7 @@ bool dataSendHandler::send_to_brewstatus() // Brew Status send_brewStatus = false; send_lock = true; - if (WiFiClass::status() == WL_CONNECTED && strlen(config.brewstatusURL) > BREWSTATUS_MIN_URL_LENGTH) - { + if (strlen(config.brewstatusURL) > BREWSTATUS_MIN_URL_LENGTH) { Log.verbose(F("Calling send to Brew Status.\r\n")); // The payload should look like this when sent to Brewstatus: @@ -322,61 +396,28 @@ bool dataSendHandler::send_to_brewstatus() { if (tilt_scanner.tilt(i)->is_loaded()) { + char gravity[10]; + char temp[6]; + tilt_scanner.tilt(i)->converted_gravity(gravity, sizeof(gravity), false); + tilt_scanner.tilt(i)->converted_temp(temp, sizeof(temp), true); // Always in Fahrenheit since we don't send units snprintf(payload, payload_size, "SG=%s&Temp=%s&Color=%s&Timepoint=%.11f&Beer=Undefined&Comment=", - tilt_scanner.tilt(i)->converted_gravity(false).c_str(), - tilt_scanner.tilt(i)->converted_temp(true).c_str(), // Only sending Fahrenheit numbers since we don't send units - tilt_color_names[i], - ((double)std::time(0) + (config.TZoffset * 3600.0)) / 86400.0 + 25569.0); - if (send_to_url(config.brewstatusURL, "", payload, "application/x-www-form-urlencoded")) - { + gravity, temp, tilt_color_names[i], ((double)std::time(0) + (config.TZoffset * 3600.0)) / 86400.0 + 25569.0); + + if (send_to_url(config.brewstatusURL, payload, content_x_www_form_urlencoded)) { Log.notice(F("Completed send to Brew Status.\r\n")); - } - else - { + } else { result = false; Log.verbose(F("Error sending to Brew Status.\r\n")); } } } } - brewStatusTicker.once(config.brewstatusPushEvery, [](){send_brewStatus = true;}); // Set up subsequent send to Brew Status + brewStatusTicker.once(config.brewstatusPushEvery, [](){data_sender.send_brewStatus = true;}); // Set up subsequent send to Brew Status send_lock = false; } return result; } -bool dataSendHandler::http_send_json(const char * url, const char * payload) -{ - int httpResponseCode; - StaticJsonDocument retval; - http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); - http.setConnectTimeout(6000); - http.setReuse(false); - - secureClient.setInsecure(); - - http.addHeader(F("Content-Type"), F("application/json")); - http.addHeader(F("Accept"), F("application/json")); - httpResponseCode = http.POST(payload); - - if (httpResponseCode >= 400) { - Log.error(F("HTTP error %d: %s, %s.\r\n"), httpResponseCode, http.errorToString(httpResponseCode).c_str(), http.getString().c_str()); - return false; - } - - if (!http.begin(secureClient, url)) { - Log.error(F("Unable to create secure connection to %s.\r\n"), url); - return false; - } - - deserializeJson(retval, http.getString().c_str()); - - http.end(); - retval.clear(); - - return true; -} - bool dataSendHandler::send_to_google() { @@ -408,12 +449,17 @@ bool dataSendHandler::send_to_google() if (tilt_scanner.tilt(i)->is_loaded()) { // Check if there is a google sheet name associated with the specific Tilt if (strlen(config.gsheets_config[i].name) > 0) { + char gravity[10]; + char temp[6]; + // If there's a sheet name saved, then we should send the data if (numSent == 0) Log.notice(F("Beginning GSheets check-in.\r\n")); payload["Beer"] = config.gsheets_config[i].name; - payload["Temp"] = tilt_scanner.tilt(i)->converted_temp(true); // Always send in Fahrenheit - payload["SG"] = tilt_scanner.tilt(i)->converted_gravity(false); + tilt_scanner.tilt(i)->converted_temp(temp, sizeof(temp), true); // Always in Fahrenheit + payload["Temp"] = temp; + tilt_scanner.tilt(i)->converted_gravity(gravity, sizeof(gravity), false); + payload["SG"] = gravity; payload["Color"] = tilt_color_names[i]; payload["Comment"] = ""; payload["Email"] = config.scriptsEmail; // The gmail email address associated with the script on google @@ -422,6 +468,9 @@ bool dataSendHandler::send_to_google() serializeJson(payload, payload_string); payload.clear(); + HTTPClient http; + WiFiClientSecure secureClient; + http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); // Follow the 301 http.setConnectTimeout(6000); // Set 6 second timeout http.setReuse(false); @@ -435,7 +484,7 @@ bool dataSendHandler::send_to_google() Log.verbose(F("Created secure connection to %s.\r\n"), config.scriptsURL); Log.verbose(F("Sending the following payload to Google Sheets (%s):\r\n\t\t%s\r\n"), tilt_color_names[i], payload_string); - http.addHeader(F("Content-Type"), F("application/json")); // Specify content-type header + http.addHeader(F("Content-Type"), content_json); // Specify content-type header httpResponseCode = http.POST(payload_string); // Send the payload if (httpResponseCode == HTTP_CODE_OK) { // HTTP_CODE_OK = 200 @@ -475,7 +524,7 @@ bool dataSendHandler::send_to_google() Log.notice(F("Submitted %l sheet%s to Google.\r\n"), numSent, (numSent== 1) ? "" : "s"); } - gSheetsTicker.once(GSCRIPTS_DELAY, [](){send_gSheets = true;}); // Set up subsequent send to Google Sheets + gSheetsTicker.once(GSCRIPTS_DELAY, [](){data_sender.send_gSheets = true;}); // Set up subsequent send to Google Sheets //tilt_scanner.init(); send_lock = false; @@ -487,48 +536,58 @@ void dataSendHandler::init_mqtt() { LCBUrl url; - if (strcmp(config.mqttBrokerHost, "") != 0 || strlen(config.mqttBrokerHost) != 0) - { - if (url.isMDNS(config.mqttBrokerHost)) { - Log.verbose(F("Initializing connection to MQTTBroker: %s (%s) on port: %d\r\n"), - config.mqttBrokerHost, - url.getIP(config.mqttBrokerHost).toString().c_str(), - config.mqttBrokerPort); - } else { - Log.verbose(F("Initializing connection to MQTTBroker: %s on port: %d\r\n"), - config.mqttBrokerHost, - config.mqttBrokerPort); - } - - if (mqtt_alreadyinit) { + // Checking for the WiFi Status is done in the data sending loop, but we also need to be sure we are connected to WiFi when we initialize the MQTT client + if (WiFiClass::status() == WL_CONNECTED) { + if(mqtt_alreadyinit) { + Log.verbose(F("MQTT already initialized. Disconnecting.\r\n")); mqttClient.disconnect(); delay(250); + } + + if (strcmp(config.mqttBrokerHost, "") != 0 || strlen(config.mqttBrokerHost) != 0) { if (url.isMDNS(config.mqttBrokerHost)) { - mqttClient.setHost(url.getIP(config.mqttBrokerHost), config.mqttBrokerPort); + Log.verbose(F("Initializing connection to MQTTBroker: %s (%s) on port: %d\r\n"), + config.mqttBrokerHost, url.getIP(config.mqttBrokerHost).toString().c_str(), config.mqttBrokerPort); } else { - mqttClient.setHost(config.mqttBrokerHost, config.mqttBrokerPort); + Log.verbose(F("Initializing connection to MQTTBroker: %s on port: %d\r\n"), + config.mqttBrokerHost, config.mqttBrokerPort); } - } else { - if (url.isMDNS(config.mqttBrokerHost)) { - mqttClient.begin(url.getIP(config.mqttBrokerHost), config.mqttBrokerPort, mqClient); + + if (mqtt_alreadyinit) { + mqttClient.disconnect(); + delay(250); + if (url.isMDNS(config.mqttBrokerHost)) { + mqttClient.setHost(url.getIP(config.mqttBrokerHost), config.mqttBrokerPort); + } else { + mqttClient.setHost(config.mqttBrokerHost, config.mqttBrokerPort); + } } else { - mqttClient.begin(config.mqttBrokerHost, config.mqttBrokerPort, mqClient); + if (url.isMDNS(config.mqttBrokerHost)) { + mqttClient.begin(url.getIP(config.mqttBrokerHost), config.mqttBrokerPort, mqClient); + } else { + mqttClient.begin(config.mqttBrokerHost, config.mqttBrokerPort, mqClient); + } } + mqtt_alreadyinit = true; + mqttClient.setKeepAlive(config.mqttPushEvery); } - mqtt_alreadyinit = true; - mqttClient.setKeepAlive(config.mqttPushEvery); } } void dataSendHandler::connect_mqtt() { - if (strlen(config.mqttUsername) > 1) - { - mqttClient.connect(config.mdnsID, config.mqttUsername, config.mqttPassword); - } - else - { - mqttClient.connect(config.mdnsID); + // Checking for the WiFi Status is done in the data sending loop, but we also need to be sure we are connected to WiFi when we connect to the MQTT broker + if (WiFiClass::status() == WL_CONNECTED) { + if(!mqtt_alreadyinit) { + // Since init is not called synchronously with the settings update when the user sets the MQTT broker, we need to + // wait until the MQTT client is initialized if it hasn't been done already. + return; + } + if (strlen(config.mqttUsername) > 1) { + mqttClient.connect(config.mdnsID, config.mqttUsername, config.mqttPassword); + } else { + mqttClient.connect(config.mdnsID); + } } } @@ -547,7 +606,7 @@ String lcburl_getAfterPath(LCBUrl url) // Get anything after the path return afterpath; } -bool dataSendHandler::send_to_url(const char *url, const char *apiKey, const char *dataToSend, const char *contentType, bool checkBody, const char* bodyCheck) +bool dataSendHandler::send_to_url(const char *url, const char *dataToSend, const char *contentType, bool checkBody, const char* bodyCheck) { // This handles the generic act of sending data to an endpoint bool retVal = false; @@ -558,6 +617,8 @@ bool dataSendHandler::send_to_url(const char *url, const char *apiKey, const cha lcburl.setUrl(url); bool validTarget = false; + // There is an issue where the built-in HTTP client for some reason won't resolve mDNS addresses. Instead, we'll + // resolve the address first, and then pass that to the client if needed. if (lcburl.isMDNS(lcburl.getHost().c_str())) { // Make sure we can resolve the address @@ -570,12 +631,11 @@ bool dataSendHandler::send_to_url(const char *url, const char *apiKey, const cha else { // If it's not mDNS all we care about is that it's http - if (lcburl.getScheme() == "http") + // if (lcburl.getScheme() == "http") validTarget = true; } - if (validTarget) - { + if (validTarget) { if (lcburl.isMDNS(lcburl.getHost().c_str())) // Use the IP address we resolved (necessary for mDNS) Log.verbose(F("Connecting to: %s at %s on port %l\r\n"), @@ -587,249 +647,291 @@ bool dataSendHandler::send_to_url(const char *url, const char *apiKey, const cha lcburl.getHost().c_str(), lcburl.getPort()); - if (client.connect(lcburl.getIP(lcburl.getHost().c_str()), lcburl.getPort())) - { - Log.verbose(F("Connected to: %s.\r\n"), lcburl.getHost().c_str()); - // Open POST connection - if (lcburl_getAfterPath(lcburl).length() > 0) - { - Log.verbose(F("POST /%s%s HTTP/1.1\r\n"), - lcburl.getPath().c_str(), - lcburl_getAfterPath(lcburl).c_str()); - } - else - { - Log.verbose(F("POST /%s HTTP/1.1\r\n"), lcburl.getPath().c_str()); - } - client.print(F("POST /")); - client.print(lcburl.getPath().c_str()); - if (lcburl_getAfterPath(lcburl).length() > 0) - { - client.print(lcburl_getAfterPath(lcburl).c_str()); - } - client.println(F(" HTTP/1.1")); - - // Begin headers - // - // Host - Log.verbose(F("Host: %s:%l\r\n"), lcburl.getHost().c_str(), lcburl.getPort()); - client.print(F("Host: ")); - client.print(lcburl.getHost().c_str()); - client.print(F(":")); - client.println(lcburl.getPort()); - // - Log.verbose(F("Connection: close\r\n")); - client.println(F("Connection: close")); - // Content - Log.verbose(F("Content-Length: %l\r\n"), strlen(dataToSend)); - client.print(F("Content-Length: ")); - client.println(strlen(dataToSend)); - // Content Type - Log.verbose(F("Content-Type: %s\r\n"), contentType); - client.print(F("Content-Type: ")); - client.println(contentType); - // API Key - if (strlen(apiKey) > 2) - { - Log.verbose(F("X-API-KEY: %s\r\n"), apiKey); - client.print(F("X-API-KEY: ")); - client.println(apiKey); - } - // Terminate headers with a blank line - Log.verbose(F("End headers.\r\n")); - client.println(); - // - // End Headers - - // Post JSON - client.println(dataToSend); - // Check the HTTP status (should be "HTTP/1.1 200 OK") - char status[32] = {0}; - client.readBytesUntil('\r', status, sizeof(status)); - client.stop(); - Log.verbose(F("Status: %s\r\n"), status); - if (strcmp(status + 9, "200 OK") == 0) - { - if (checkBody == true) // We can do additional checks here - { - // Check body - String response = String(status); - if (response.indexOf(bodyCheck) >= 0) - { - Log.verbose(F("Response body ok.\r\n")); - retVal = true; - } - else - { - Log.error(F("Unexpected body content: %s\r\n"), response.c_str()); - retVal = false; - } - } - else - { - Log.verbose(F("Post to %s was successful.\r\n"), lcburl.getHost().c_str()); - retVal = true; + // This may crash if we're allocating too much memory on the heap, but if I can get away with this + // it's the easiest solution. + HTTPClient http; + WiFiClientSecure secureClient; + secureClient.setInsecure(); // Don't perform certificate validation. This opens up MITM attacks, but I don't have memory otherwise. + + // Determine if the URL is HTTP or HTTPS and initialize HTTPClient + if (lcburl.getScheme() == "https") { + http.begin(secureClient, url); // HTTPS + } else { + http.begin(url); // HTTP + } + + // Set headers + http.addHeader(F("Content-Type"), contentType); + http.addHeader(F("Accept"), content_json); + + char userAgent[128]; + snprintf(userAgent, sizeof(userAgent), + "tiltbridge/%s (branch %s; build %s)", + version(), + branch(), + build() + ); + http.setUserAgent(userAgent); + + yield(); // Yield before we lock up the radio + + // Send the request + int httpResponseCode; + httpResponseCode = http.POST(dataToSend); + + // Optionally check the response + bool result = false; + if (httpResponseCode > 0) { + // HTTP header has been sent and Server response header has been handled + Log.verbose(F("HTTP Response code: %d\r\n"), httpResponseCode); + + if (checkBody) { + String response = http.getString(); + if (response.indexOf(bodyCheck) >= 0) { + result = true; + } else { + Log.error(F("Body check failed. Body: %s\r\n"), response.c_str()); } + } else { + result = (httpResponseCode == HTTP_CODE_OK); } - else - { - Log.error(F("Unexpected status: %s\r\n"), status); - retVal = false; - } - } - else - { - Log.warning(F("Connection failed, Host: %s, Port: %l (Err: %d)\r\n"), - lcburl.getHost().c_str(), lcburl.getPort(), client.connected()); - retVal = false; + } else { + Log.error(F("Error on sending POST: %s\r\n"), http.errorToString(httpResponseCode).c_str()); + Log.error(F("Connection failed, Host: %s, Port: %l\r\n"), lcburl.getHost().c_str(), lcburl.getPort()); } - } - else - { + + // Close connection + http.end(); + delay(100); // Give garbage collection time to run + return result; + + } else { Log.error(F("Invalid target: %s.\r\n"), url); } - } - else - { + } else { Log.notice(F("No URL provided, or no data to send.\r\n")); - retVal = false; } - return retVal; + // If we reached here, the send was unsuccessful + return false; } -bool dataSendHandler::send_to_mqtt() -{ - // TODO: (JSON) Come back and tighten this up + +bool dataSendHandler::send_to_mqtt() { bool result = false; - StaticJsonDocument<512> payload; - mqttClient.loop(); - if (send_mqtt && ! send_lock) - { - // MQTT - send_mqtt = false; - send_lock = true; - if (strcmp(config.mqttBrokerHost, "") != 0 || strlen(config.mqttBrokerHost) != 0) - { - Log.verbose(F("Publishing available results to MQTT Broker.\r\n")); - // Function sends three payloads with the first two designed to - // support autodiscovery and configuration on Home Assistant. - // General payload formatted as json when sent to mqTT: - //{"Color":"Black","SG":"1.0180","Temp":"73.0","fermunits":"SG","tempunits":"F","timeStamp":1608745710} - // - // 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++) - { - if (tilt_scanner.tilt(i)->is_loaded()) - { - char tilt_topic[50] = {'\0'}; - snprintf(tilt_topic, 50, "%s/tilt_%s", - config.mqttTopic, - tilt_color_names[i]); - - for (uint8_t j = 0; j < 3; j++) - { - char m_topic[90] = {'\0'}; - char tilt_name[15] = {'\0'}; - char tilt_sensor_name[35] = {'\0'}; - char uniq_id[30] = {'\0'}; - char unit[10] = {'\0'}; - bool retain = false; - - strcat(tilt_name, "Tilt "); - strcat(tilt_name, tilt_color_names[i]); - - if (j < 2 ) - { - JsonObject device = payload.createNestedObject("device"); - device["identifiers"] = tilt_color_names[i]; - device["name"] = tilt_name; - } - - switch (j) - { - case 0: //Home Assistant Config Topic for Temperature - sprintf(m_topic, "homeassistant/sensor/%s_tilt_%s/temperature/config", - config.mqttTopic, - tilt_color_names[i]); - payload["dev_cla"] = "temperature"; - strcat(unit, "\u00b0"); - strcat(unit, config.tempUnit); - payload["unit_of_meas"] = unit; - payload["ic"] = "mdi:thermometer"; - payload["stat_t"] = tilt_topic; - strcat(tilt_sensor_name, "Tilt Temperature - "); - strcat(tilt_sensor_name, tilt_color_names[i]); - payload["name"] = tilt_sensor_name; - payload["val_tpl"] = "{{value_json.Temp}}"; - snprintf(uniq_id, 30, "tiltbridge_tilt%sT", - tilt_color_names[i]); - payload["uniq_id"] = uniq_id; - retain = true; - break; - case 1: //Home Assistant Config Topic for Sp Gravity - sprintf(m_topic, "homeassistant/sensor/%s_tilt_%sG/sp_gravity/config", - config.mqttTopic, - tilt_color_names[i]); - //payload["dev_cla"] = "None"; - payload["unit_of_meas"] = "SG"; - //payload["ic"] = ""; - payload["stat_t"] = tilt_topic; - strcat(tilt_sensor_name, "Tilt Specific Gravity - "); - strcat(tilt_sensor_name, tilt_color_names[i]); - payload["name"] = tilt_sensor_name; - payload["val_tpl"] = "{{value_json.SG}}"; - snprintf(uniq_id, 30, "tiltbridge_tilt%sG", - tilt_color_names[i]); - payload["uniq_id"] = uniq_id; - retain = true; - break; - case 2: //General payload with sensor data - strcat(m_topic, tilt_topic); - char current_grav[8] = {'\0'}; - char current_temp[5] = {'\0'}; - strcpy(current_grav, tilt_scanner.tilt(i)->converted_gravity(false).c_str()); - strcpy(current_temp, tilt_scanner.tilt(i)->converted_temp(false).c_str()); - payload["Color"] = tilt_color_names[i]; - payload["timeStamp"] = (int)std::time(0); - payload["fermunits"] = "SG"; - payload["SG"] = current_grav; - payload["Temp"] = current_temp; - payload["tempunits"] = config.tempUnit; - retain = false; - break; - } - char payload_string[320] = {'\0'}; - serializeJson(payload, payload_string); + if (strcmp(config.mqttBrokerHost, "") == 0 || strlen(config.mqttBrokerHost) == 0) { + // No MQTT broker configured + return false; + } - Log.verbose(F("Topic: %s\r\n"), m_topic); - Log.verbose(F("Message: %s\r\n"), payload_string); + if (!mqttClient.connected()) { + Log.warning(F("MQTT disconnected. Attempting to reconnect to MQTT Broker in loop\r\n")); + connect_mqtt(); + } else { + mqttClient.loop(); + } - if (!mqttClient.connected() && j == 0) - { - Log.warning(F("MQTT disconnected. Attempting to reconnect to MQTT Broker\r\n")); - connect_mqtt(); - } + if (send_mqtt && !send_lock) { + send_mqtt = false; + send_lock = true; - result = mqttClient.publish(m_topic, payload_string, retain, 0); - delay(10); + Log.verbose(F("Publishing available results to MQTT Broker.\r\n")); - payload.clear(); - } - } - } - if (result) { - Log.notice(F("Completed publish to MQTT Broker.\r\n")); - } else { - result = false; // There was an error with the previous send - Log.verbose(F("Error publishing to MQTT Broker.\r\n")); + for (uint8_t i = 0; i < TILT_COLORS; i++) { + if (tilt_scanner.tilt(i)->is_loaded()) { + prepare_and_send_payloads(i); } } - mqttTicker.once(config.mqttPushEvery, [](){send_mqtt = true;}); // Set up subsequent send to MQTT + + mqttTicker.once(config.mqttPushEvery, [](){ data_sender.send_mqtt = true; }); send_lock = false; } return result; } + +void dataSendHandler::prepare_and_send_payloads(uint8_t tilt_index) { + char tilt_topic[50] = {'\0'}; + snprintf(tilt_topic, 50, "%s/tilt_%s", config.mqttTopic, tilt_color_names[tilt_index]); + + // Prepare and send each of the four payloads + prepare_temperature_payload(tilt_color_names[tilt_index], tilt_topic); + prepare_gravity_payload(tilt_color_names[tilt_index], tilt_topic); + prepare_battery_payload(tilt_color_names[tilt_index], tilt_topic); + prepare_general_payload(tilt_index, tilt_topic); +} + +void dataSendHandler::enrich_announcement(const char* topic, const char* tilt_color, StaticJsonDocument<512>& payload) { + payload["stat_t"] = topic; + char deviceName[20]; + snprintf(deviceName, sizeof(deviceName), "Tilt %s", tilt_color); + payload["dev"]["name"] = deviceName; + payload["dev"]["ids"] = tilt_color; + payload["dev"]["mdl"] = "Tilt Hydrometer"; + payload["dev"]["mf"] = "Baron Brew Equipment LLC"; + payload["dev"]["sw"] = version(); + payload["dev"]["sa"] = "Brewery"; // Suggested Area + + char ip_address_url[25] = "http://"; + { + char ip[16]; + sprintf(ip, "%d.%d.%d.%d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]); + strncat(ip_address_url, ip, 16); + strcat(ip_address_url, "/"); + } + + + payload["dev"]["cu"] = ip_address_url; + // model and hw_version could be added, but it would require the Tilt object to determine Tilt vs. Tilt Pro + + + payload["json_attr_t"] = topic; + payload["json_attr_tpl"] = "{ \"Uptime\": \"{{ value_json.timeStamp }}\" }\n"; + + +} + + +void dataSendHandler::prepare_temperature_payload(const char* tilt_color, const char* tilt_topic) { + //Home Assistant Config Topic for Temperature + char m_topic[90]; + char tilt_sensor_name[35]; + char uniq_id[30]; + char unit[10] = "\u00b0"; // Unicode for degree symbol + StaticJsonDocument<512> payload; + + // Construct the MQTT topic string for temperature + sprintf(m_topic, "homeassistant/sensor/%s_tilt_%s/temperature/config", config.mqttTopic, tilt_color); + + // Set up payload fields + strcat(unit, config.tempUnit); // Append temperature unit after degree symbol + payload["dev_cla"] = "temperature"; + payload["unit_of_meas"] = unit; + payload["ic"] = "mdi:thermometer-lines"; + + // Construct sensor name + snprintf(tilt_sensor_name, sizeof(tilt_sensor_name), "Tilt Temperature - %s", tilt_color); + payload["name"] = tilt_sensor_name; + + // Value template + payload["val_tpl"] = "{{value_json.Temp}}"; + + // Unique ID + snprintf(uniq_id, sizeof(uniq_id), "tiltbridge_tilt%sT", tilt_color); + payload["uniq_id"] = uniq_id; + + enrich_announcement(tilt_topic, tilt_color, payload); + // Serialize and publish + publish_to_mqtt(m_topic, payload, true); // Retain flag set to true +} + + +void dataSendHandler::prepare_gravity_payload(const char* tilt_color, const char* tilt_topic) { + //Home Assistant Config Topic for Sp Gravity + char m_topic[90]; + char tilt_sensor_name[35]; + char uniq_id[30]; + StaticJsonDocument<512> payload; + + // Construct the MQTT topic string for specific gravity + sprintf(m_topic, "homeassistant/sensor/%s_tilt_%sG/sp_gravity/config", config.mqttTopic, tilt_color); + + // Set up payload fields + payload["unit_of_meas"] = "SG"; + payload["ic"] = "mdi:trending-down"; + + // Construct sensor name + snprintf(tilt_sensor_name, sizeof(tilt_sensor_name), "Tilt Specific Gravity - %s", tilt_color); + payload["name"] = tilt_sensor_name; + + // Value template + payload["val_tpl"] = "{{value_json.SG}}"; + + // Unique ID + snprintf(uniq_id, sizeof(uniq_id), "tiltbridge_tilt%sG", tilt_color); + payload["uniq_id"] = uniq_id; + + enrich_announcement(tilt_topic, tilt_color, payload); + // Serialize and publish + publish_to_mqtt(m_topic, payload, true); // Retain flag set to true +} + +void dataSendHandler::prepare_battery_payload(const char* tilt_color, const char* tilt_topic) { + //Home Assistant Config Topic for Weeks On Battery + char m_topic[90]; + char tilt_sensor_name[35]; + char uniq_id[30]; + StaticJsonDocument<512> payload; + + // Construct the MQTT topic string for weeks on battery + sprintf(m_topic, "homeassistant/sensor/%s_tilt_%sWoB/weeks_on_battery/config", config.mqttTopic, tilt_color); + + // Set up payload fields + payload["unit_of_meas"] = "weeks"; + payload["ic"] = "mdi:battery"; + + // Construct sensor name + snprintf(tilt_sensor_name, sizeof(tilt_sensor_name), "Tilt Weeks On Battery - %s", tilt_color); + payload["name"] = tilt_sensor_name; + + // Value template + payload["val_tpl"] = "{{value_json.WoB}}"; + + // Unique ID + snprintf(uniq_id, sizeof(uniq_id), "tiltbridge_tilt%sWoB", tilt_color); + payload["uniq_id"] = uniq_id; + + enrich_announcement(tilt_topic, tilt_color, payload); + // Serialize and publish + publish_to_mqtt(m_topic, payload, true); // Retain flag set to true +} + +void dataSendHandler::prepare_general_payload(uint8_t tilt_index, const char* tilt_topic) { + //General payload with sensor data + char m_topic[90]; + char gravity[10]; + char temp[6]; + char battery_str[4]; // large enough for 0-255 and the null terminator + StaticJsonDocument<512> payload; + tiltHydrometer* current_tilt = tilt_scanner.tilt(tilt_index); + + // Construct the MQTT topic string for general sensor data + strcpy(m_topic, tilt_topic); + + // Populate payload with sensor data + payload["Color"] = tilt_color_names[tilt_index]; + payload["timeStamp"] = (int)std::time(0); + payload["fermunits"] = "SG"; + current_tilt->converted_gravity(gravity, 10, false); + payload["SG"] = gravity; + current_tilt->converted_temp(temp, 6, false); + payload["Temp"] = temp; + payload["tempunits"] = config.tempUnit; + current_tilt->get_weeks_battery(battery_str, 4); + payload["WoB"] = battery_str; + + // Serialize and publish + publish_to_mqtt(m_topic, payload, false); // Retain flag set to false for general data +} + + +bool dataSendHandler::publish_to_mqtt(const char* topic, StaticJsonDocument<512>& payload, bool retain) { + char payload_string[512]; + serializeJson(payload, payload_string); + + if (!mqttClient.connected()) { + Log.warning(F("MQTT disconnected. Attempting to reconnect to MQTT Broker\r\n")); + connect_mqtt(); + } + + bool result = mqttClient.publish(topic, payload_string, retain, 0); + if(result) { + Log.verbose(F("Published to MQTT\r\n")); + } else { + Log.error(F("Failed to publish to MQTT\r\n")); + } + delay(10); + return result; +} + diff --git a/src/sendData.h b/src/sendData.h index 66ee709..34177db 100644 --- a/src/sendData.h +++ b/src/sendData.h @@ -1,24 +1,9 @@ #ifndef TILTBRIDGE_SENDDATA_H #define TILTBRIDGE_SENDDATA_H -#include "serialhandler.h" -#include "wifi_setup.h" -#include "jsonconfig.h" -#include "main.h" // DEBUG - -#include -#include -#include - -#include -#include -#include #include -#include -#include -#include -#include -#include +#include +#include #define GSCRIPTS_DELAY (10 * 60) // 10 minute delay between pushes to Google Sheets directly #define BREWERS_FRIEND_DELAY (15 * 60) // 15 minute delay between pushes to Brewer's Friend @@ -52,7 +37,6 @@ class dataSendHandler void init(); void init_mqtt(); void process(); - bool mqtt_alreadyinit = false; bool send_to_google(); bool send_to_localTarget(); @@ -61,25 +45,58 @@ class dataSendHandler bool send_to_mqtt(); bool send_to_bf_and_bf(uint8_t which_bf); // Handler for both Brewer's Friend and Brewfather bool send_to_grainfather(); + bool send_to_bf_and_bf(); + void send_to_cloud(); + // Send Timers + Ticker cloudTargetTicker; + Ticker localTargetTicker; + Ticker brewersFriendTicker; + Ticker brewfatherTicker; + Ticker userTargetTicker; + Ticker grainfatherTicker; + Ticker brewStatusTicker; Ticker taplistioTicker; + Ticker gSheetsTicker; + Ticker mqttTicker; + // Send Semaphores + bool send_cloudTarget = false; + bool send_localTarget = false; + bool send_brewersFriend = false; + bool send_brewfather = false; + bool send_userTarget = false; + bool send_grainfather = false; + bool send_brewStatus = false; bool send_taplistio = false; + bool send_gSheets = false; + bool send_mqtt = false; private: - void connect_mqtt(); - bool send_to_url(const char *url, const char *apiKey, const char *dataToSend, const char *contentType, bool checkBody = false, const char *bodyCheck = ""); - bool http_send_json(const char *url, const char * payload); - HTTPClient http; - WiFiClient client; + bool send_lock = false; + + bool send_to_url(const char *url, const char *dataToSend, const char *contentType, bool checkBody = false, const char *bodyCheck = ""); + + // MQTT Stuff WiFiClient mqClient; - WiFiClientSecure secureClient; + bool mqtt_alreadyinit = false; + + void connect_mqtt(); + bool publish_to_mqtt(const char* topic, StaticJsonDocument<512>& payload, bool retain); + void prepare_and_send_payloads(uint8_t tilt_index); + void prepare_temperature_payload(const char* tilt_color, const char* tilt_topic); + void prepare_gravity_payload(const char* tilt_color, const char* tilt_topic); + void prepare_battery_payload(const char* tilt_color, const char* tilt_topic); + void prepare_general_payload(uint8_t tilt_index, const char* tilt_topic); + void enrich_announcement(const char* topic, const char* tilt_color, StaticJsonDocument<512>& payload); + }; -bool send_to_bf_and_bf(); -void send_to_cloud(); extern dataSendHandler data_sender; +constexpr auto content_json = "application/json"; +constexpr auto content_x_www_form_urlencoded = "application/x-www-form-urlencoded"; + #endif //TILTBRIDGE_SENDDATA_H diff --git a/src/send_targets/taplistio.cpp b/src/send_targets/taplistio.cpp deleted file mode 100644 index 0b4be43..0000000 --- a/src/send_targets/taplistio.cpp +++ /dev/null @@ -1,199 +0,0 @@ -#include -#include -#include "Ticker.h" -#include - -#include -// #include -#include -#include -#include - -#include "jsonconfig.h" -#include "tilt/tiltScanner.h" -#include "sendData.h" - -extern bool send_lock; // TODO - Move this to dataSendHandler - - -// bool dataSendHandler::send_to_taplistio() -// { -// StaticJsonDocument<192> j; -// char payload_string[192]; -// int httpResponseCode; -// bool result = true; - -// // See if it's our time to send. -// if (!send_taplistio) { -// return false; -// } else if (send_lock) { -// return false; -// } - -// // Attempt to send. -// send_taplistio = false; -// send_lock = true; - -// if (WiFiClass::status() != WL_CONNECTED) { -// Log.verbose(F("taplist.io: Wifi not connected, skipping send.\r\n")); -// taplistioTicker.once(config.taplistioPushEvery, [](){data_sender.send_taplistio = true;}); -// send_lock = false; -// return false; -// } - -// char userAgent[128]; -// snprintf(userAgent, sizeof(userAgent), -// "tiltbridge/%s (branch %s; build %s)", -// version(), -// branch(), -// build() -// ); - -// for (uint8_t i = 0; i < TILT_COLORS; i++) { - -// if (!tilt_scanner.tilt(i)->is_loaded()) { -// continue; -// } - -// j["Color"] = tilt_color_names[i]; -// j["Temp"] = tilt_scanner.tilt(i)->converted_temp(false); -// j["SG"] = tilt_scanner.tilt(i)->converted_gravity(false); -// j["temperature_unit"] = "F"; -// j["gravity_unit"] = "G"; - -// serializeJson(j, payload_string); - -// http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); -// http.setConnectTimeout(6000); -// http.setReuse(false); -// secureClient.setInsecure(); - -// Log.verbose(F("taplist.io: Sending %s Tilt to %s\r\n"), tilt_color_names[i], config.taplistioURL); - -// if (!http.begin(secureClient, config.taplistioURL)) { -// Log.error(F("taplist.io: Unable to create secure connection to %s\r\n"), config.taplistioURL); -// result = false; -// break; -// } - -// http.addHeader(F("Content-Type"), F("application/json")); -// http.setUserAgent(userAgent); -// httpResponseCode = http.POST(payload_string); - -// if (httpResponseCode < HTTP_CODE_OK || httpResponseCode > HTTP_CODE_NO_CONTENT) { -// Log.error(F("taplist.io: send %s Tilt failed (%d): %s. Response:\r\n%s\r\n"), -// tilt_color_names[i], -// httpResponseCode, -// http.errorToString(httpResponseCode).c_str(), -// http.getString().c_str()); -// result = false; -// } else { -// Log.verbose(F("taplist.io: %s Tilt: success!\r\n"), tilt_color_names[i]); -// } -// } - -// taplistioTicker.once(config.taplistioPushEvery, [](){send_taplistio = true;}); -// send_lock = false; -// return result; -// } - - -bool dataSendHandler::send_to_taplistio() -{ - StaticJsonDocument<192> j; - char payload_string[192]; - char taplistio_url[768]; - char auth_header[64]; - int httpResponseCode; - bool result = true; - - // See if it's our time to send. - if (!send_taplistio) { - return false; - } else if (send_lock) { - Log.verbose(F("taplist.io: send lock set.\r\n")); - return false; - } - - - // Since we're only using .once timers, we can just detach/recreate every time and be fine - taplistioTicker.detach(); - - // Attempt to send. - send_taplistio = false; - send_lock = true; - - if (WiFiClass::status() != WL_CONNECTED) { - Log.verbose(F("taplist.io: Wifi not connected, skipping send.\r\n")); - taplistioTicker.once(config.taplistioPushEvery, [](){data_sender.send_taplistio = true;}); - send_lock = false; - return false; - } - - char userAgent[128]; - snprintf(userAgent, sizeof(userAgent), - "tiltbridge/%s (branch %s; build %s)", - version(), - branch(), - build() - ); - - - for (uint8_t i = 0; i < TILT_COLORS; i++) { - - if (!tilt_scanner.tilt(i)->is_loaded()) { - continue; - } - - j["Color"] = tilt_color_names[i]; - j["Temp"] = tilt_scanner.tilt(i)->converted_temp(false); - j["SG"] = tilt_scanner.tilt(i)->converted_gravity(false); - j["temperature_unit"] = "F"; - j["gravity_unit"] = "G"; - - serializeJson(j, payload_string); - - Log.verbose(F("taplist.io: Sending %s Tilt to %s\r\n"), tilt_color_names[i], config.taplistioURL); - - yield(); // Yield before we lock up the radio - - WiFiClientSecure *client = new WiFiClientSecure; - if(client) { - client->setInsecure(); - { - // Add a scoping block for HTTPClient https to make sure it is destroyed before WiFiClientSecure *client is - HTTPClient http; - - http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); - http.setConnectTimeout(6000); - http.setReuse(false); - - if (http.begin(*client, config.taplistioURL)) { - http.addHeader(F("Content-Type"), F("application/json")); - http.setUserAgent(userAgent); - httpResponseCode = http.POST(payload_string); - - if (httpResponseCode < HTTP_CODE_OK || httpResponseCode > HTTP_CODE_NO_CONTENT) { - Log.error(F("taplist.io: send %s Tilt failed (%d): %s. Response:\r\n%s\r\n"), - tilt_color_names[i], - httpResponseCode, - http.errorToString(httpResponseCode).c_str(), - http.getString().c_str()); - result = false; - } else { - Log.verbose(F("taplist.io: %s Tilt: success!\r\n"), tilt_color_names[i]); - } - http.end(); - } else { - Log.error(F("taplist.io: Unable to create connection\r\n")); - result = false; - } - } - delete client; - } - } - - taplistioTicker.once(config.taplistioPushEvery, [](){data_sender.send_taplistio = true;}); - send_lock = false; - return result; -} diff --git a/src/serialhandler.cpp b/src/serialhandler.cpp index 1d2a388..1e2cfc2 100644 --- a/src/serialhandler.cpp +++ b/src/serialhandler.cpp @@ -1,4 +1,3 @@ -#include #include #include "serialhandler.h" diff --git a/src/serialhandler.h b/src/serialhandler.h index 59234be..3c5ad05 100644 --- a/src/serialhandler.h +++ b/src/serialhandler.h @@ -1,8 +1,6 @@ #ifndef _SERIALLOG_H #define _SERIALLOG_H -#include - #if DOTELNET == true #include #include diff --git a/src/tilt/tiltHydrometer.cpp b/src/tilt/tiltHydrometer.cpp index 0c4ac98..d6ea67a 100644 --- a/src/tilt/tiltHydrometer.cpp +++ b/src/tilt/tiltHydrometer.cpp @@ -45,48 +45,35 @@ tiltHydrometer::tiltHydrometer(uint8_t color) } // tiltHydrometer -uint8_t tiltHydrometer::uuid_to_color_no(std::string uuid) +uint8_t tiltHydrometer::uuid_to_color_no(const char* uuid) { + if (uuid == nullptr) { + return TILT_NONE; + } - if (uuid == TILT_COLOR_RED_UUID) - { + if (strcmp(uuid, TILT_COLOR_RED_UUID) == 0) return TILT_COLOR_RED; - } - else if (uuid == TILT_COLOR_GREEN_UUID) - { + else if (strcmp(uuid, TILT_COLOR_GREEN_UUID) == 0) return TILT_COLOR_GREEN; - } - else if (uuid == TILT_COLOR_BLACK_UUID) - { + else if (strcmp(uuid, TILT_COLOR_BLACK_UUID) == 0) return TILT_COLOR_BLACK; - } - else if (uuid == TILT_COLOR_PURPLE_UUID) - { + else if (strcmp(uuid, TILT_COLOR_PURPLE_UUID) == 0) return TILT_COLOR_PURPLE; - } - else if (uuid == TILT_COLOR_ORANGE_UUID) - { + else if (strcmp(uuid, TILT_COLOR_ORANGE_UUID) == 0) return TILT_COLOR_ORANGE; - } - else if (uuid == TILT_COLOR_BLUE_UUID) - { + else if (strcmp(uuid, TILT_COLOR_BLUE_UUID) == 0) return TILT_COLOR_BLUE; - } - else if (uuid == TILT_COLOR_YELLOW_UUID) - { + else if (strcmp(uuid, TILT_COLOR_YELLOW_UUID) == 0) return TILT_COLOR_YELLOW; - } - else if (uuid == TILT_COLOR_PINK_UUID) - { + else if (strcmp(uuid, TILT_COLOR_PINK_UUID) == 0) return TILT_COLOR_PINK; - } else - { return TILT_NONE; - } + } + bool tiltHydrometer::set_values(uint16_t i_temp, uint16_t i_grav, uint8_t i_tx_pwr, int8_t current_rssi) { double d_temp; @@ -203,27 +190,33 @@ bool tiltHydrometer::set_values(uint16_t i_temp, uint16_t i_grav, uint8_t i_tx_p return true; } -std::string tiltHydrometer::converted_gravity(bool use_raw_gravity) +void tiltHydrometer::converted_gravity(char* output, size_t output_size, bool use_raw_gravity) { - char rnd_gravity[7]; + if (output == nullptr || output_size < 7) { + // Handle error: output is null or not large enough + return; + } + const uint16_t grav_scalar = (tilt_pro) ? 10000 : 1000; + float gravity_value = use_raw_gravity ? gravity : gravity_smoothed; + float converted_value = static_cast(gravity_value) / grav_scalar; - if (use_raw_gravity) - snprintf(rnd_gravity, 7, "%.4f", (float)gravity / grav_scalar); - else - snprintf(rnd_gravity, 7, "%.4f", (float)gravity_smoothed / grav_scalar); - std::string output = rnd_gravity; - return output; + // Using snprintf to format the string and handle buffer overflow + snprintf(output, output_size, "%.4f", converted_value); } void tiltHydrometer::to_json_string(char *json_string, bool use_raw_gravity) { StaticJsonDocument j; + char gravity_str[10]; + char temp_str[6]; j["color"] = tilt_color_names[m_color]; - j["temp"] = converted_temp(false); + converted_temp(temp_str, sizeof(temp_str), false); + j["temp"] = temp_str; j["tempUnit"] = is_celsius() ? "C" : "F"; - j["gravity"] = converted_gravity(use_raw_gravity); + converted_gravity(gravity_str, sizeof(gravity_str), use_raw_gravity); + j["gravity"] = gravity_str; j["weeks_on_battery"] = weeks_since_last_battery_change; j["sends_battery"] = receives_battery; j["high_resolution"] = tilt_pro; @@ -239,20 +232,35 @@ void tiltHydrometer::to_json_string(char *json_string, bool use_raw_gravity) serializeJson(j, json_string, TILT_DATA_SIZE); } -std::string tiltHydrometer::converted_temp(bool fahrenheit_only) +void tiltHydrometer::converted_temp(char* output, size_t output_size, bool fahrenheit_only) { - char rnd_temp[5]; + if (output == nullptr || output_size < 6) { // 6 to accommodate '-0.0\0' or similar + // Handle error: output is null or not large enough + return; + } + const float temp_scalar = (tilt_pro) ? 10.0f : 1.0f; - double d_temp = (double)temp / temp_scalar; + double d_temp = static_cast(temp) / temp_scalar; - if (is_celsius() && !fahrenheit_only) + if (is_celsius() && !fahrenheit_only) { d_temp = (d_temp - 32) * 5 / 9; + } + + snprintf(output, output_size, "%.1f", d_temp); +} - snprintf(rnd_temp, 5, "%.1f", d_temp); - std::string output = rnd_temp; - return output; +void tiltHydrometer::get_weeks_battery(char* output, size_t output_size) +{ + if (output == nullptr || output_size == 0) { + // Handle error: output is null or size is zero + return; + } + + // Using snprintf to safely convert weeks_since_last_battery_change to a string + snprintf(output, output_size, "%u", static_cast(weeks_since_last_battery_change)); } + bool tiltHydrometer::is_celsius() const { return strcmp(config.tempUnit, "C") == 0; diff --git a/src/tilt/tiltHydrometer.h b/src/tilt/tiltHydrometer.h index ee60a23..fc8bf83 100644 --- a/src/tilt/tiltHydrometer.h +++ b/src/tilt/tiltHydrometer.h @@ -1,8 +1,6 @@ #ifndef TILTBRIDGE_TILTHYDROMETER_H #define TILTBRIDGE_TILTHYDROMETER_H -//#include "jsonconfig.h" -#include #include #define TILT_DATA_SIZE 477 // JSON size of a Tilt @@ -42,13 +40,14 @@ class tiltHydrometer explicit tiltHydrometer(uint8_t color); bool set_values(uint16_t i_temp, uint16_t i_grav, uint8_t i_tx_pwr, int8_t current_rssi); - std::string converted_gravity(bool use_raw_gravity); + void converted_gravity(char* output, size_t output_size, bool use_raw_gravity); void to_json_string(char *json_string, bool use_raw_gravity); - std::string converted_temp(bool fahrenheit_only); + void converted_temp(char* output, size_t output_size, bool fahrenheit_only); + void get_weeks_battery(char* output, size_t output_size); bool is_celsius() const; bool is_loaded(); - static uint8_t uuid_to_color_no(std::string data); + static uint8_t uuid_to_color_no(const char* uuid); uint16_t temp; uint16_t gravity; diff --git a/src/tilt/tiltScanner.cpp b/src/tilt/tiltScanner.cpp index 47a9c26..4669674 100644 --- a/src/tilt/tiltScanner.cpp +++ b/src/tilt/tiltScanner.cpp @@ -2,8 +2,12 @@ // Created by John Beeler on 5/12/18. // -#include "tiltScanner.h" #include +#include +#include + +#include "tiltScanner.h" + // Create the scanner BLEScan *pBLEScan; @@ -136,10 +140,8 @@ uint8_t tiltScanner::load_tilt_from_advert_hex(const std::string &advert_string_ } m_color = tiltHydrometer::uuid_to_color_no(m_color_arr); - if (m_color == TILT_NONE) - { // We didn't match the uuid to a color (should only happen if new colors are released) + if (m_color == TILT_NONE) // We didn't match the uuid to a color (should only happen if new colors are released) return TILT_NONE; - } uint16_t temp = std::strtoul(temp_arr, nullptr, 16); uint16_t gravity = std::strtoul(grav_arr, nullptr, 16); diff --git a/src/tilt/tiltScanner.h b/src/tilt/tiltScanner.h index 6ef4aaa..f5b1877 100644 --- a/src/tilt/tiltScanner.h +++ b/src/tilt/tiltScanner.h @@ -5,16 +5,10 @@ #ifndef TILTBRIDGE_TILTSCANNER_H #define TILTBRIDGE_TILTSCANNER_H -#include "tiltHydrometer.h" -#include "serialhandler.h" +#include + #include "tiltHydrometer.h" -#include -#include -#include -#include -#include -#include #define BLE_SCAN_TIME 3 // Seconds to scan diff --git a/src/uptime.cpp b/src/uptime.cpp index b161206..9135ca4 100644 --- a/src/uptime.cpp +++ b/src/uptime.cpp @@ -2,6 +2,8 @@ // Created by Lee Bussy on 12/31/20 // +#include + #include "uptime.h" static int refresh = UPTIME_REFRESH * 1000; diff --git a/src/uptime.h b/src/uptime.h index a81d63f..d31115e 100644 --- a/src/uptime.h +++ b/src/uptime.h @@ -5,7 +5,7 @@ #ifndef _UPTIME_H #define _UPTIME_H -#include +// #include #define UPTIME_REFRESH 1 diff --git a/src/watchButtons.cpp b/src/watchButtons.cpp index d26fb61..3e0b994 100644 --- a/src/watchButtons.cpp +++ b/src/watchButtons.cpp @@ -1,3 +1,4 @@ +#include "bridge_lcd.h" #include "watchButtons.h" static unsigned long wifiButtonTime = 0; // Button press timer diff --git a/src/watchButtons.h b/src/watchButtons.h index b87b850..022d748 100644 --- a/src/watchButtons.h +++ b/src/watchButtons.h @@ -1,10 +1,6 @@ #ifndef _WATCHBUTTONS_H #define _WATCHBUTTONS_H -#include "bridge_lcd.h" -#include -#include - // We use these for LCD_TFT_ESPI or LCD_SSD1306 #define BOARD_RESET_BUTTON_GPIO 35 // (Soft) Reset button #define WIFI_RESET_BUTTON_GPIO 0 // Boot button diff --git a/src/wifi_setup.cpp b/src/wifi_setup.cpp index 5d295d8..191f793 100644 --- a/src/wifi_setup.cpp +++ b/src/wifi_setup.cpp @@ -1,13 +1,11 @@ -#include #include #include -#include #include #include + #include "bridge_lcd.h" #include "jsonconfig.h" -#include "http_server.h" // Make sure this include is after WiFiManager -#include "serialhandler.h" +#include "http_server.h" #include "wifi_setup.h" diff --git a/src/wifi_setup.h b/src/wifi_setup.h index 98e1bc3..099fba7 100644 --- a/src/wifi_setup.h +++ b/src/wifi_setup.h @@ -4,16 +4,6 @@ #define WIFI_SETUP_AP_NAME "TiltBridgeAP" #define WIFI_SETUP_AP_PASS "tiltbridge" // Must be 8-63 chars -//#include "bridge_lcd.h" -//#include "serialhandler.h" -//#include "jsonconfig.h" -//#include - -//#include -//#include -//#include -//#include "http_server.h" // Make sure this include is after AsyncWiFiManager - #define WEB_SERVER_PORT 80