28 April 2020
27 April 2020
26 April 2020
Find3 is a framework that uses Wifi and Bluetooth signal levels (RSSI) from devices relative to each other and then applies statistical methods such as machine learning in order to track devices indoors. A0 rundown and tutorial of Find3 can be found in the IoT section.
This sketch is an alternative Find3 client for the ESP, forked from the official repository but with a few features added and some refactoring.
/* 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 <http://www.gnu.org/licenses/>. */ /////////////////////////////////////////////////////////////////////////// // 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 <WiFiClientSecure.h> #include <WiFi.h> #else #include <ESP8266WiFi.h> #endif #include <WiFiUdp.h> #include <NTPClient.h> #include <ArduinoOTA.h> #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 <ArduinoJson.h> // 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 <BLEDevice.h> #include <BLEUtils.h> #include <BLEScan.h> 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<JsonObject>(); 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(); }