/* This file is part of esp-find3-client by Sylwester aka DatanoiseTV. The original source can be found at https://github.com/DatanoiseTV/esp-find3-client. 26/04/2020: Adjustments by Wizardry and Steamworks. esp-find3-client is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. esp-find3-client is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with esp-find3-client. If not, see . */ /////////////////////////////////////////////////////////////////////////// // CONFIGURATION // /////////////////////////////////////////////////////////////////////////// // Set to the WiFi AP name. #define WIFI_SSID "" // Set to the WiFi AP password. #define WIFI_PSK "" // Set to 1 for learning mode. #define MODE_LEARNING 1 #define LOCATION "" // Family name. #define FAMILY_NAME "" // BLE requires large app partition or the sketch will not fit. // Please choose: // * Tools -> Partition scheme -> Minimal SPIFFS (1.9MB APP / 190KB SPIFFS) // and set to 1 to enable BLE. #define USE_BLE 1 #define BLE_SCANTIME 5 // Official server: cloud.internalpositioning.com #define FIND_HOST "cloud.internalpositioning.com" // Official port: 443 and SSL set to 1 #define FIND_PORT 443 // Whether to use SSL for the HTTP connection. // Set to 1 for official cloud server. #define USE_HTTP_SSL 1 // Timeout connecting to find3 server expressed in milliseconds. #define HTTP_TIMEOUT 2500 // The NTP server to use for time updates. #define NTP_HOST "pool.ntp.org" // The offset in seconds from UTC, ie: 3600 for +1 Hour. #define UTC_OFFSET 2 * 3600 // The password to use for OTA updates. #define OTA_PASSWORD "" // Set to 1 to enable. Used for verbose debugging. #define DEBUG 1 /////////////////////////////////////////////////////////////////////////// // INTERNALS // /////////////////////////////////////////////////////////////////////////// #ifdef ARDUINO_ARCH_ESP32 #include #include #else #include #endif #include #include #include #if defined(ARDUINO_ARCH_ESP8266) #define GET_CHIP_ID() String(ESP.getChipId(), HEX) #elif defined(ARDUINO_ARCH_ESP32) #define GET_CHIP_ID() String(((uint16_t)(ESP.getEfuseMac()>>32)), HEX) #endif #define uS_TO_S_FACTOR 1000000 /* Conversion factor for micro seconds to seconds */ #define ARDUINOJSON_USE_LONG_LONG 1 #include // Automagically disable BLE on ESP8266 #if defined(ARDUINO_ARCH_ESP8266) && !defined(ARDUINO_ARCH_ESP32) #define USE_BLE 0 #endif #if defined(ARDUINO_ARCH_ESP32) && (USE_BLE == 1) #include #include #include class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { // Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str()); } }; #endif #ifdef ARDUINO_ARCH_ESP32 RTC_DATA_ATTR int bootCount = 0; #endif WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, NTP_HOST, UTC_OFFSET, 60000); // Retrieves the WiFi MAC address. String getWiFiMAC() { String result; byte mac[6]; WiFi.macAddress(mac); for (int i = 5; i > -1; --i) { result += String(mac[i], HEX); if (i != 0) { result += ":"; } } return result; } void SubmitWiFi(void) { Serial.println("[ INFO ]\tWiFi MAC: " + getWiFiMAC()); String request; StaticJsonDocument<256> jsonBuffer; JsonObject root = jsonBuffer.to(); root["d"] = "esp-" + GET_CHIP_ID(); root["f"] = FAMILY_NAME; root["t"] = timeClient.getEpochTime(); #if (MODE_LEARNING == 1) Serial.println("[ iNFO ]\tLearning enabled, sending learning data."); root["l"] = LOCATION; #endif JsonObject data = root.createNestedObject("s"); Serial.println("[ INFO ]\tWiFi scan starting.."); int n = WiFi.scanNetworks(false, true); Serial.println("[ INFO ]\tWiFi Scan finished."); if (n == 0) { Serial.println("[ ERROR ]\tNo networks found"); } else { Serial.print("[ INFO ]\t"); Serial.print(n); Serial.println(" WiFi networks found."); JsonObject wifi_network = data.createNestedObject("wifi"); for (int i = 0; i < n; ++i) { wifi_network[WiFi.BSSIDstr(i)] = WiFi.RSSI(i); } #if (USE_BLE == 1) Serial.println("[ INFO ]\tBLE scan starting.."); BLEDevice::init(""); BLEAddress btMAC = BLEDevice::getAddress(); Serial.println("[ INFO ]\tBT MAC: " + String(btMAC.toString().c_str())); BLEScan* pBLEScan = BLEDevice::getScan(); // create new scan pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); pBLEScan->setActiveScan(true); // active scan uses more power, but get results faster BLEScanResults foundDevices = pBLEScan->start(BLE_SCANTIME); Serial.print("[ INFO ]\t"); Serial.print(foundDevices.getCount()); Serial.println(" BLE devices found."); JsonObject bt_network = data.createNestedObject("bluetooth"); for (int i = 0; i < foundDevices.getCount(); i++) { std::string mac = foundDevices.getDevice(i).getAddress().toString(); bt_network[(String)mac.c_str()] = (int)foundDevices.getDevice(i).getRSSI(); } #else Serial.println("[ INFO ]\tBLE scan skipped (BLE disabled).."); #endif // USE_BLE serializeJson(root, request); #if (DEBUG == 1) Serial.println("[ DEBUG ]\t" + request); #endif #if (USE_HTTP_SSL == 1) WiFiClientSecure client; #else WiFiClient client; #endif if (!client.connect(FIND_HOST, FIND_PORT)) { Serial.println("[ WARN ]\tConnection to server failed, restarting in 5s..."); delay(5000); ESP.restart(); } // We now create a URI for the request String url = "/data"; Serial.print("[ INFO ]\tRequesting URL: "); Serial.println(url); // This will send the request to the server client.print(String("POST ") + url + " HTTP/1.1\r\n" + "Host: " + FIND_HOST + "\r\n" + "Content-Type: application/json\r\n" + "Content-Length: " + request.length() + "\r\n\r\n" + request + "\r\n\r\n" ); client.flush(); unsigned long timeout = millis(); while (client.available() == 0) { if (millis() - timeout > HTTP_TIMEOUT) { Serial.println("[ ERROR ]\tHTTP Client Timeout!"); client.stop(); return; } } // Check HTTP status char status[60] = {0}; client.readBytesUntil('\r', status, sizeof(status)); if (strcmp(status, "HTTP/1.1 200 OK") != 0) { Serial.print(F("[ ERROR ]\tUnexpected Response: ")); Serial.println(status); return; } else { Serial.println(F("[ INFO ]\tGot a 200 OK.")); } char endOfHeaders[] = "\r\n\r\n"; if (!client.find(endOfHeaders)) { Serial.println(F("[ ERROR ]\t Invalid Response")); return; } else { Serial.println("[ INFO ]\tLooks like a valid response."); } Serial.println("[ INFO ]\tClosing connection."); Serial.println("============================================================="); } } void setup() { Serial.begin(115200); delay(1000); #if defined(ARDUINO_ARCH_ESP32) && (USE_BLE == 1) Serial.println("Find3 ESP client by DatanoiseTV (WiFi + BLE support.)"); #else Serial.println("Find3 ESP client by DatanoiseTV (WiFi support WITHOUT BLE.)"); #endif Serial.print("[ INFO ]\tESP ID is: "); Serial.println("esp-" + GET_CHIP_ID()); // Hostname defaults to esp8266-[ChipID] ArduinoOTA.setHostname(String("esp-" + GET_CHIP_ID()).c_str()); // Set the OTA password ArduinoOTA.setPassword(OTA_PASSWORD); ArduinoOTA.onStart([]() { Serial.println("============================================================="); switch (ArduinoOTA.getCommand()) { case U_FLASH: // Sketch Serial.println("[ INFO ]\tOTA start updating sketch."); break; #if defined(ARDUINO_ARCH_ESP8266) case U_FS: #elif defined(ARDUINO_ARCH_ESP32) case U_SPIFFS: #endif Serial.println("[ INFO ]\tOTA start updating filesystem."); break; default: Serial.println("[ WARN ]\tUnknown OTA update type."); break; } }); ArduinoOTA.onEnd([]() { Serial.println("[ INFO ]\tOTA update complete."); Serial.println("============================================================="); ESP.restart(); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf("[ INFO ]\tOTA update progress: %u%%\r", (progress / (total / 100))); }); ArduinoOTA.onError([](ota_error_t error) { Serial.printf("[ ERROR ]\tOTA update error [%u]: ", error); switch (error) { case OTA_AUTH_ERROR: Serial.println("OTA authentication failed"); break; case OTA_BEGIN_ERROR: Serial.println("OTA begin failed"); break; case OTA_CONNECT_ERROR: Serial.println("OTA connect failed"); break; case OTA_RECEIVE_ERROR: Serial.println("OTA receive failed"); break; case OTA_END_ERROR: Serial.println("OTA end failed"); break; default: Serial.println("Unknown OTA failure"); break; } ESP.restart(); }); } void loop() { // If WiFi is not connected, attempt to reconnect and if that fails then restart. if (WiFi.status() != WL_CONNECTED) { Serial.println("[ WARN ]\tWiFi not connected, retrying..."); WiFi.disconnect(); #if defined(ARDUINO_ARCH_ESP32) WiFi.setHostname(String("esp-" + GET_CHIP_ID()).c_str()); #elif defined(ARDUINO_ARCH_ESP8266) WiFi.hostname(String("esp-" + GET_CHIP_ID()).c_str()); #endif WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PSK); while (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("[ ERROR ]\tFailed to connect to Wifi, rebooting in 5s..."); delay(5000); ESP.restart(); } Serial.println("[ INFO ]\tStarting OTA..."); ArduinoOTA.begin(); Serial.println("[ INFO ]\tStarting NTP..."); timeClient.begin(); } ArduinoOTA.handle(); timeClient.update(); SubmitWiFi(); }