├── .gitignore ├── ESP32WiFiSnifferApp ├── data │ ├── This folder (data) is for SPIFFS use only.txt │ └── server │ │ ├── sniffer.jpg │ │ ├── room.json │ │ ├── config.json │ │ ├── style.css │ │ ├── layout.html │ │ ├── general.js │ │ ├── tabs.js │ │ └── posDet.js ├── calibration │ └── calibration.xls ├── sdcardcontent │ └── server │ │ ├── sniffer.jpg │ │ ├── room.json │ │ ├── config.json │ │ ├── style.css │ │ ├── layout.html │ │ ├── general.js │ │ ├── tabs.js │ │ └── posDet.js ├── include │ ├── taskFunctionsForSlave.hpp │ ├── taskFunctionsForMaster.hpp │ ├── interactWiFi.hpp │ ├── interactLoRa.hpp │ ├── interactFileSystem.hpp │ ├── interactWiFiSniffer.hpp │ ├── interactData.hpp │ ├── interactOLED.hpp │ ├── interactNTP.hpp │ ├── interactWebServer.hpp │ ├── generalFunctions.hpp │ ├── interactSPIFFS.hpp │ ├── interactSD.hpp │ ├── globalVariables.hpp │ └── dataTypes.hpp ├── test │ └── README ├── lib │ └── README ├── platformio.ini └── src │ ├── taskFunctionsForSlave.cpp │ ├── interactWiFi.cpp │ ├── interactWebServer.cpp │ ├── interactLoRa.cpp │ ├── interactNTP.cpp │ ├── interactSPIFFS.cpp │ ├── taskFunctionsForMaster.cpp │ ├── interactData.cpp │ ├── generalFunctions.cpp │ ├── main.cpp │ ├── interactOLED.cpp │ ├── interactWiFiSniffer.cpp │ ├── interactSD.cpp │ └── interactFileSystem.cpp ├── ESP32WiFiSnifferManual.pdf └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .pio/ 2 | .vscode/ 3 | ESP32WiFiSnifferManual/ -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/data/This folder (data) is for SPIFFS use only.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ESP32WiFiSnifferManual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huaminghuangtw/ESP32WiFiSnifferApp/HEAD/ESP32WiFiSnifferManual.pdf -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/data/server/sniffer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huaminghuangtw/ESP32WiFiSnifferApp/HEAD/ESP32WiFiSnifferApp/data/server/sniffer.jpg -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/calibration/calibration.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huaminghuangtw/ESP32WiFiSnifferApp/HEAD/ESP32WiFiSnifferApp/calibration/calibration.xls -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/sdcardcontent/server/sniffer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huaminghuangtw/ESP32WiFiSnifferApp/HEAD/ESP32WiFiSnifferApp/sdcardcontent/server/sniffer.jpg -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/data/server/room.json: -------------------------------------------------------------------------------- 1 | { 2 | "room_contour":[{"x":0.00,"y":0.00}, 3 | {"x":0.00,"y":3.00}, 4 | {"x":4.58,"y":3.00}, 5 | {"x":4.58,"y":0.52}, 6 | {"x":2.77,"y":0.52}, 7 | {"x":2.77,"y":0.00}] 8 | } -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/sdcardcontent/server/room.json: -------------------------------------------------------------------------------- 1 | { 2 | "room_contour":[{"x":0.00,"y":0.00}, 3 | {"x":0.00,"y":3.00}, 4 | {"x":4.58,"y":3.00}, 5 | {"x":4.58,"y":0.52}, 6 | {"x":2.77,"y":0.52}, 7 | {"x":2.77,"y":0.00}] 8 | } -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/include/taskFunctionsForSlave.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | /* ------------------------ 5 | Task functions for Slave 6 | ------------------------ */ 7 | 8 | 9 | void monitorWiFiDevices( void* parameter ); // Function for task running on core 0. 10 | void sendAndLogWiFiDeviceData( void* parameter ); // Function for task running on core 1. -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/include/taskFunctionsForMaster.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | /* ------------------------- 5 | Task functions for Master 6 | ------------------------- */ 7 | 8 | 9 | void receiveAndLogWiFiDeviceData( void* parameter ); // Function for task running on core 0. 10 | void hostWebServer( void* parameter ); // Function for task running on core 1. 11 | -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/include/interactWiFi.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* External heaader files */ 4 | #include "WiFi.h" 5 | 6 | /* Project heaader files */ 7 | #include "globalVariables.hpp" 8 | 9 | 10 | // WiFi connection related helper functions 11 | void scanWiFiNetworks(); 12 | void connectToWiFi( const char* ssid, const char* password ); 13 | void disconnectWiFi(); 14 | String translateEncryptionType( wifi_auth_mode_t encryptionType ); -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/include/interactLoRa.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* External heaader files */ 4 | #include 5 | #include 6 | 7 | /* Project heaader files */ 8 | #include "dataTypes.hpp" 9 | 10 | 11 | extern unsigned int packetCounter1; 12 | extern unsigned int packetCounter2; 13 | extern unsigned int packetCounter3; 14 | extern unsigned int packetCounter4; 15 | 16 | 17 | void initializeLoRa(); 18 | void sendToMasterDevice( wifiDeviceData data ); 19 | void receiveFromSlaveDevice( int packetSize ); -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/data/server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ssid":"YourSSID", 3 | "pswd":"YourPassword", 4 | "am_i_master":false, 5 | "demo":true, 6 | "master_MAC":"00:00:00:00:0F:05", 7 | "master_coords":{"x":0.1,"y":0.1}, 8 | "number_of_slaves":4, 9 | "slave_MACs":["00:00:00:00:0F:01","00:00:00:00:0F:02","00:00:00:00:0F:03","00:00:00:00:0F:04"], 10 | "slave_coords":[{"x":0.1,"y":0.53},{"x":0.1,"y":2.9},{"x":4.47,"y":2.9},{"x":2.65,"y":0.6}], 11 | "photo_device_MAC":"F0:F0:F0:F0:F0:F0", 12 | "RSSI_1m":-46, 13 | "path_loss_exp":2.4991 14 | } -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/sdcardcontent/server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ssid":"YourSSID", 3 | "pswd":"YourPassword", 4 | "am_i_master":false, 5 | "demo":true, 6 | "master_MAC":"00:00:00:00:0F:05", 7 | "master_coords":{"x":0.1,"y":0.1}, 8 | "number_of_slaves":4, 9 | "slave_MACs":["00:00:00:00:0F:01","00:00:00:00:0F:02","00:00:00:00:0F:03","00:00:00:00:0F:04"], 10 | "slave_coords":[{"x":0.1,"y":0.53},{"x":0.1,"y":2.9},{"x":4.47,"y":2.9},{"x":2.65,"y":0.6}], 11 | "photo_device_MAC":"F0:F0:F0:F0:F0:F0", 12 | "RSSI_1m":-46, 13 | "path_loss_exp":2.4991 14 | } -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/include/interactFileSystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* External heaader files */ 4 | #include 5 | /* To use 64-bit (long long) integers with ArduinoJson, you must set ARDUINOJSON_USE_LONG_LONG to 1. 6 | See https://arduinojson.org/v6/api/config/use_long_long/ */ 7 | #define ARDUINOJSON_USE_LONG_LONG 1 8 | #include 9 | 10 | /* Project heaader files */ 11 | //... 12 | 13 | 14 | JsonObject getConfigFile( fs::FS &fs, DynamicJsonDocument& jsondoc, const char* path_to_file ); 15 | void printConfigFile( const char* path_to_file ); 16 | void printCurrentConfigData(); 17 | void configESPfromJSON( const char* path_to_file ); -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/include/interactWiFiSniffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* External heaader files */ 4 | #include 5 | #include 6 | 7 | /* Project heaader files */ 8 | #include "dataTypes.hpp" 9 | #include "globalVariables.hpp" 10 | 11 | 12 | void sniffForWiFiPacketData( void* buf, wifi_promiscuous_pkt_type_t type ); 13 | void setWifiPromiscuousMode(); 14 | void changeWifiChannel(); 15 | std::vector parseWiFiDeviceData( wifiDeviceData& data ); 16 | void printWiFiDeviceData( std::vector data ); 17 | void printWiFiDeviceData( wifiDeviceData& data ); 18 | void printWiFiDevicePayload( wifiDevicePayload& payload ); 19 | void printCombinedWiFiDeviceData( combinedWiFiDeviceData& combinedData ); -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/include/interactData.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* External heaader files */ 4 | //... 5 | 6 | /* Project heaader files */ 7 | #include "dataTypes.hpp" 8 | #include "globalVariables.hpp" 9 | #include "generalFunctions.hpp" 10 | 11 | 12 | // Data processor for demo mode. Collect only one single device specified in photo_device_MAC field in config.js. 13 | // The position of this device is known in advance to validate the position determination algorithm. 14 | void demoModeDataProcessor( const wifiDevicePayload payload, int8_t* rawRSSI ); 15 | 16 | // Data processor for normal mode. Collect all random devices passing by. 17 | void normalModeDataProcessor( const wifiDevicePayload payload, std::vector &buffer ); -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/include/interactOLED.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* External heaader files */ 4 | #include 5 | #include 6 | #include 7 | 8 | /* Project heaader files */ 9 | #include "globalVariables.hpp" 10 | 11 | 12 | // OLED related functions 13 | void initializeOLED(); 14 | void OLEDdisplayForOLEDInit(); 15 | void OLEDdisplayForSDCardInit(); 16 | void OLEDdisplayForSPIFFSInit(); 17 | void OLEDdisplayForLoRaInit(); 18 | void OLEDdisplayForLoRaSenderInit(); 19 | void OLEDdisplayForLoRaReceiverInit(); 20 | void OLEDdisplayForLoRaSender( wifiDeviceData data ); 21 | void OLEDdisplayForLoRaReceiver( int slaveID ); 22 | int getMaxPages(); 23 | void OLEDdisplayForSlaveDevicesMac(); 24 | void OLEDdisplayForWiFiConnection(); 25 | void OLEDdisplayForIPAddress(); 26 | void OLEDdisplayForMasterInit(); 27 | void OLEDdisplayForWebServer(); -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/include/interactNTP.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* External heaader files */ 4 | #include 5 | 6 | /* Project heaader files */ 7 | #include "interactWiFi.hpp" 8 | 9 | 10 | extern const char* NTP_SERVER; 11 | extern const char* TZ_INFO; 12 | 13 | 14 | extern struct tm timeinfo; 15 | extern struct timeval tv; 16 | // Unix Time in seconds 17 | extern time_t now; 18 | // Unix Time in milliseconds 19 | extern uint64_t time_ms; 20 | // Unix Time in microseconds 21 | extern uint64_t time_us; 22 | // Millisecond fraction 23 | extern uint64_t ms_frac; 24 | // Microsecond fraction 25 | extern uint64_t us_frac; 26 | 27 | 28 | bool callNTPServer(int sec); 29 | uint64_t getUnixTimeinMilliseconds(); 30 | uint64_t getUnixTimeinMicroseconds(); 31 | void getTextFormatTime(char *buf); 32 | void getTextFormatTimeinMilliseconds(char *buf); 33 | void getTextFormatTimeinMicroseconds(char *buf); 34 | void printLocalTime(tm localTime); -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/include/interactWebServer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* External header files */ 4 | #include 5 | 6 | /* Project heaader files */ 7 | //... 8 | 9 | 10 | 11 | extern bool ledState; 12 | extern int loggedDataCounter; 13 | 14 | 15 | 16 | // Sends an update to all clients connected to the websocket about the current state. 17 | void notifyWebSocketClients( AsyncWebSocket *wserver ); 18 | 19 | // Handles the different possible websocket events that can occur. 20 | void handleWebSocketEvent( AsyncWebSocket *wsserver, AsyncWebSocketClient *wsclient, 21 | AwsEventType type, void *arg, uint8_t *data, size_t len ); 22 | 23 | // Adds a websocket to the asynchronous webserver. 24 | void addWebSocket( AsyncWebSocket& wsserver, AsyncWebServer& server, AwsEventHandler wsHandler ); 25 | 26 | // Initializes and starts the asynchronous webserver. 27 | void initWebServer( AsyncWebServer& server ); 28 | 29 | // Sets an mDNS (local) domain name for the server. 30 | void addMDNS( const char* mDNSname ); 31 | 32 | // Cleans up resources after uncorrectly closed or lost connections. 33 | void cleanUpWebSocketClients( AsyncWebSocket& wsserver ); -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ESP32WiFiSnifferApp 2 | =================== 3 | 4 | 5 | 6 | 9 | 10 |
7 | An interdisciplinary team project of the course Software Lab at Technical University of Munich (TUM), aiming to map WiFi signals to increase localization accuracy with embedded IoT device ESP32 by tracking the position of nearby WiFi enabled devices. 8 |
11 | 12 | ### Demo 13 | 14 |

15 | 16 |

17 | 18 | --- 19 | 20 | ### Overview 21 | 22 | > Please see the documentation [*ESP32WiFiSnifferManual.pdf*](ESP32WiFiSnifferManual.pdf) for instruction in using this software and details about this project. 23 | 24 | ![Poster](https://user-images.githubusercontent.com/43208378/114070043-e8ecff00-989f-11eb-8f9e-8b783817cf50.png) 25 | 26 | --- 27 | 28 | ### Presentation 29 | [![Watch the video](https://user-images.githubusercontent.com/43208378/114065534-0cfa1180-989b-11eb-937f-96531686b1b8.png)](https://drive.google.com/file/d/1OOYYimOC2WSo_yMeU6AgRschL-jhe60d/view?usp=sharing) 30 | 31 | --- 32 | 33 | ### Contact 34 | If you have any question or suggestion, feel free to contact me at huaming.huang.tw@gmail.com. Contributions are also welcomed. Please open a [pull-request](https://github.com/huaminghuangtw/ESP32WiFiSnifferApp/compare) or an [issue](https://github.com/huaminghuangtw/ESP32WiFiSnifferApp/issues/new) in this repository. 35 | -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | default_envs = Master, Slave_1, Slave_2, Slave_3, Slave_4 13 | 14 | [env] 15 | platform = espressif32 16 | framework = arduino 17 | monitor_speed = 115200 18 | lib_deps = 19 | adafruit/Adafruit SSD1306 @ ^2.4.0 20 | adafruit/Adafruit GFX Library @ ^1.10.1 21 | adafruit/Adafruit BusIO @ ^1.5.0 22 | Wire @ ^1.0.1 23 | bblanchon/ArduinoJson @ ^6.17.2 24 | sandeepmistry/LoRa@^0.8.0 25 | adafruit/Adafruit BusIO@^1.7.1 26 | me-no-dev/ESP Async WebServer @ ^1.2.3 27 | me-no-dev/AsyncTCP @ ^1.1.1 28 | 29 | [env:Master] 30 | board = ttgo-lora32-v1 31 | upload_port = COM3 32 | build_flags = -D Master 33 | 34 | [env:Slave_1] 35 | board = ttgo-lora32-v1 36 | upload_port = COM6 37 | build_flags = -D Slave_1 38 | 39 | [env:Slave_2] 40 | board = ttgo-lora32-v1 41 | upload_port = COM7 42 | build_flags = -D Slave_2 43 | 44 | [env:Slave_3] 45 | board = ttgo-t-beam 46 | upload_port = COM5 47 | build_flags = -D Slave_3 48 | 49 | [env:Slave_4] 50 | board = heltec_wifi_lora_32_V2 51 | upload_port = COM4 52 | build_flags = -D Slave_4 53 | -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/include/generalFunctions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* External heaader files */ 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | /* To use 64-bit (long long) integers with ArduinoJson, you must set ARDUINOJSON_USE_LONG_LONG to 1. 10 | See https://arduinojson.org/v6/api/config/use_long_long/ */ 11 | #define ARDUINOJSON_USE_LONG_LONG 1 12 | #include 13 | #include 14 | #include 15 | 16 | /* Project heaader files */ 17 | #include "dataTypes.hpp" 18 | #include "globalVariables.hpp" 19 | 20 | 21 | // Functions which could not be categorized (yet) should be declared here. 22 | void stringToMACnumber( const char* charmac, MacAddr& mac ); 23 | void MACnumberTostring( char* stringMac, MacAddr& mac ); 24 | void timestampTostring( char* stringTimestamp, unsigned long timestamp ); 25 | void packetCounterTostring( char* stringPacketCounter, unsigned int& packetCounter ); 26 | void RSSITostring( char* stringRSSI, int8_t& RSSI ); 27 | std::string uint32_to_string( uint32_t value ); 28 | std::string uint64_to_string( uint64_t value ); 29 | void print_uint64_t(uint64_t num); 30 | JsonObject convertCombinedWifiDeviceDataToJSON( StaticJsonDocument<512>& staticJsonDoc, combinedWiFiDeviceData& deviceData ); 31 | void sendCombinedDeviceDataToClient( AsyncWebSocket& wsserver, combinedWiFiDeviceData& combinedData ); 32 | combinedWiFiDeviceData generateRandomRecordsFromSlaves(); 33 | combinedWiFiDeviceData generateRecordAtPointLNSMmodelFixedMac( float x_free, float y_free, int RSSIc, float path_loss_exp ); -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/include/interactSPIFFS.hpp: -------------------------------------------------------------------------------- 1 | #if defined(Slave_3) || defined(Slave_4) 2 | 3 | 4 | #pragma once 5 | 6 | 7 | /* External heaader files */ 8 | #include "FS.h" 9 | #include "SPIFFS.h" 10 | /* To use 64-bit (long long) integers with ArduinoJson, you must set ARDUINOJSON_USE_LONG_LONG to 1. 11 | See https://arduinojson.org/v6/api/config/use_long_long/ */ 12 | #define ARDUINOJSON_USE_LONG_LONG 1 13 | #include 14 | 15 | /* Project heaader files */ 16 | //... 17 | 18 | 19 | /* -------------------------------------------------------------------------------------------------------------- 20 | Use SPIFFS with ESP32 21 | *Before uploading sketch to the board: 22 | Step #1: Create a folder called "data" (it should be on the same level as src folder) and put files here. 23 | Step #2: Upload files to SPIFFS by either running "Upload Filesystem Image" task in PlatformIO IDE's GUI 24 | or use command "pio run -t uploadfs" in terminal. 25 | Step #3: Finally upload your sketch to the board. 26 | -------------------------------------------------------------------------------------------------------------- */ 27 | 28 | 29 | void initializeSIPFFS(); 30 | void listDir( fs::FS &fs, const char * dirname, uint8_t levels ); 31 | void readFile( fs::FS &fs, const char * path ); 32 | void writeFile( fs::FS &fs, const char * path, const char * message ); 33 | void appendFile( fs::FS &fs, const char * path, const char * message ); 34 | void logSPISSF( std::string dataMessage, const char* path_to_file ); 35 | void logSPIFFSFileHeader( std::string headerText ); 36 | void renameFile( fs::FS &fs, const char * path1, const char * path2 ); 37 | void deleteFile( fs::FS &fs, const char * path ); 38 | void testFileIO( fs::FS &fs, const char * path ); 39 | 40 | 41 | #endif -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/include/interactSD.hpp: -------------------------------------------------------------------------------- 1 | #if defined(Master) || defined(Slave_1) || defined(Slave_2) 2 | 3 | 4 | #pragma once 5 | 6 | /* External heaader files */ 7 | #include 8 | /* To use 64-bit (long long) integers with ArduinoJson, you must set ARDUINOJSON_USE_LONG_LONG to 1. 9 | See https://arduinojson.org/v6/api/config/use_long_long/ */ 10 | #define ARDUINOJSON_USE_LONG_LONG 1 11 | #include 12 | 13 | /* Project heaader files */ 14 | #include "dataTypes.hpp" 15 | #include "globalVariables.hpp" 16 | #include "generalFunctions.hpp" 17 | 18 | 19 | // Initialize SD card 20 | void initializeSDCard(); 21 | 22 | // Write to the SD card 23 | void writeFile( SDFileSystemClass &fs, const char* path, const char* message ); 24 | 25 | // Append data to the SD card 26 | void appendFile( SDFileSystemClass &fs, const char* path, const char* message ); 27 | 28 | // Write the chip readings to the SD card 29 | void logSDCard( std::string dataMessage, const char* path_to_file ); 30 | 31 | // Read a file from the SD card 32 | void readFile( SDFileSystemClass &fs, const char* path ); 33 | 34 | // List dir from the SD card 35 | void listDir( SDFileSystemClass &fs, const char* dirname, uint8_t levels ); 36 | 37 | // Add header to the file 38 | void logSDFileHeader( std::string headerText, const char* path_to_file ); 39 | 40 | // Log acquired device data to binary logfile on the SD card 41 | void logToBinFileOnSD( wifiDeviceData deviceData, const char* path_to_file ); 42 | 43 | // Read logged device data from binary logfile on the SD card 44 | void readBinFileFromSD( wifiDeviceData& deviceData, const char* path_to_file ); 45 | 46 | // Convert binary file to csv file 47 | void convertBinToCsvFile( const char* path_to_bin, const char* path_to_csv ); 48 | 49 | // Log to a csv file, overloaded function for different purposes. 50 | void logToCsvFileOnSD( wifiDeviceData& data, const char* path_to_file ); 51 | void logToCsvFileOnSD( combinedWiFiDeviceData& data, const char* path_to_file ); 52 | 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/data/server/style.css: -------------------------------------------------------------------------------- 1 | /* Formatting based on html tags. */ 2 | html { 3 | font-family: Arial, Helvetica, sans-serif; 4 | text-align: center; 5 | } 6 | h1 { 7 | font-size: 1.8rem; 8 | color: white; 9 | } 10 | h2{ 11 | font-size: 1.5rem; 12 | font-weight: bold; 13 | color: #143642; 14 | } 15 | body { 16 | margin: 0; 17 | } 18 | tbody { 19 | max-height: 500px; 20 | height: 500px; 21 | width:100%; 22 | display: block; 23 | overflow-y:scroll; 24 | } 25 | th { 26 | padding-bottom:10px; 27 | } 28 | tbody tr { 29 | display: table; 30 | width:100%; 31 | table-layout:fixed; 32 | } 33 | thead { 34 | display: table; 35 | width:98.5%; 36 | table-layout:fixed; 37 | } 38 | table { 39 | width: 100%; 40 | } 41 | 42 | /* Formatting based on classes. */ 43 | .topnav { 44 | overflow: hidden; 45 | background-color: #143642; 46 | font-size: 28; 47 | padding-top: 0px; 48 | padding-bottom: 0px; 49 | margin: 0px; 50 | } 51 | .content { 52 | max-width: 93%; 53 | margin: 0 auto; 54 | padding-top: 2%; 55 | } 56 | .button { 57 | padding-top: 15px; 58 | padding-bottom: 15px; 59 | width: 180px; 60 | font-size: 24px; 61 | text-align: center; 62 | outline: none; 63 | color: #fff; 64 | background-color: #0f8b8d; 65 | border: none; 66 | border-radius: 5px; 67 | -webkit-touch-callout: none; 68 | -webkit-user-select: none; 69 | -khtml-user-select: none; 70 | -moz-user-select: none; 71 | -ms-user-select: none; 72 | user-select: none; 73 | -webkit-tap-highlight-color: rgba(0,0,0,0); 74 | } 75 | .button:active { 76 | background-color: #0f8b8d; 77 | box-shadow: 2 2px #CDCDCD; 78 | transform: translateY(2px); 79 | } 80 | .state { 81 | font-size: 1.5rem; 82 | color:#8c8c8c; 83 | font-weight: bold; 84 | } 85 | .tabButton { 86 | background-color: #555; 87 | color: white; 88 | float: left; 89 | border: none; 90 | outline: none; 91 | cursor: pointer; 92 | padding-top: 15px; 93 | padding-bottom: 20px; 94 | font-size: 24px; 95 | width: 15%; 96 | } 97 | .tabButton:hover { 98 | background-color: #777; 99 | } 100 | .tabcontent { 101 | background-color: #143642; 102 | color: white; 103 | display: none; 104 | padding-top: 5%; 105 | padding-bottom: 5%; 106 | padding-right: 20px; 107 | padding-left: 20px; 108 | } 109 | .tab { 110 | overflow:hidden; 111 | padding-bottom:0px; 112 | } 113 | .tablecontainer { 114 | max-height: 550px; 115 | height: 550px; 116 | } 117 | .tablecontainer thead th { 118 | position: sticky; 119 | top: 0; 120 | } 121 | 122 | /* Formatting based on individual IDs. */ 123 | #Left { 124 | width: 35%; 125 | float: left; 126 | } 127 | #Right { 128 | width: 65%; 129 | float: right; 130 | } 131 | #startButton { 132 | float: right; 133 | width: 100px; 134 | } 135 | #Map { 136 | background-color: #e8e8e8; 137 | width: 55vw; 138 | height: 60vh; 139 | } -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/sdcardcontent/server/style.css: -------------------------------------------------------------------------------- 1 | /* Formatting based on html tags. */ 2 | html { 3 | font-family: Arial, Helvetica, sans-serif; 4 | text-align: center; 5 | } 6 | h1 { 7 | font-size: 1.8rem; 8 | color: white; 9 | } 10 | h2{ 11 | font-size: 1.5rem; 12 | font-weight: bold; 13 | color: #143642; 14 | } 15 | body { 16 | margin: 0; 17 | } 18 | tbody { 19 | max-height: 500px; 20 | height: 500px; 21 | width:100%; 22 | display: block; 23 | overflow-y:scroll; 24 | } 25 | th { 26 | padding-bottom:10px; 27 | } 28 | tbody tr { 29 | display: table; 30 | width:100%; 31 | table-layout:fixed; 32 | } 33 | thead { 34 | display: table; 35 | width:98.5%; 36 | table-layout:fixed; 37 | } 38 | table { 39 | width: 100%; 40 | } 41 | 42 | /* Formatting based on classes. */ 43 | .topnav { 44 | overflow: hidden; 45 | background-color: #143642; 46 | font-size: 28; 47 | padding-top: 0px; 48 | padding-bottom: 0px; 49 | margin: 0px; 50 | } 51 | .content { 52 | max-width: 93%; 53 | margin: 0 auto; 54 | padding-top: 2%; 55 | } 56 | .button { 57 | padding-top: 15px; 58 | padding-bottom: 15px; 59 | width: 180px; 60 | font-size: 24px; 61 | text-align: center; 62 | outline: none; 63 | color: #fff; 64 | background-color: #0f8b8d; 65 | border: none; 66 | border-radius: 5px; 67 | -webkit-touch-callout: none; 68 | -webkit-user-select: none; 69 | -khtml-user-select: none; 70 | -moz-user-select: none; 71 | -ms-user-select: none; 72 | user-select: none; 73 | -webkit-tap-highlight-color: rgba(0,0,0,0); 74 | } 75 | .button:active { 76 | background-color: #0f8b8d; 77 | box-shadow: 2 2px #CDCDCD; 78 | transform: translateY(2px); 79 | } 80 | .state { 81 | font-size: 1.5rem; 82 | color:#8c8c8c; 83 | font-weight: bold; 84 | } 85 | .tabButton { 86 | background-color: #555; 87 | color: white; 88 | float: left; 89 | border: none; 90 | outline: none; 91 | cursor: pointer; 92 | padding-top: 15px; 93 | padding-bottom: 20px; 94 | font-size: 24px; 95 | width: 15%; 96 | } 97 | .tabButton:hover { 98 | background-color: #777; 99 | } 100 | .tabcontent { 101 | background-color: #143642; 102 | color: white; 103 | display: none; 104 | padding-top: 5%; 105 | padding-bottom: 5%; 106 | padding-right: 20px; 107 | padding-left: 20px; 108 | } 109 | .tab { 110 | overflow:hidden; 111 | padding-bottom:0px; 112 | } 113 | .tablecontainer { 114 | max-height: 550px; 115 | height: 550px; 116 | } 117 | .tablecontainer thead th { 118 | position: sticky; 119 | top: 0; 120 | } 121 | 122 | /* Formatting based on individual IDs. */ 123 | #Left { 124 | width: 35%; 125 | float: left; 126 | } 127 | #Right { 128 | width: 65%; 129 | float: right; 130 | } 131 | #startButton { 132 | float: right; 133 | width: 100px; 134 | } 135 | #Map { 136 | background-color: #e8e8e8; 137 | width: 55vw; 138 | height: 60vh; 139 | } -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/data/server/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ESP32 Sniffer App 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |

ESP32 Sniffer App

20 |
21 | 22 |
23 | 24 |
25 |

Output - GPIO 25

26 |

LED: %STATE%

27 |

28 | 29 |

Download logfiles

30 | 31 |

32 |
33 |
34 | 35 | 95 | 96 |
97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/sdcardcontent/server/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ESP32 Sniffer App 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |

ESP32 Sniffer App

20 |
21 | 22 |
23 | 24 |
25 |

Output - GPIO 25

26 |

LED: %STATE%

27 |

28 | 29 |

Download logfiles

30 | 31 |

32 |
33 |
34 | 35 | 95 | 96 |
97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/src/taskFunctionsForSlave.cpp: -------------------------------------------------------------------------------- 1 | /* Header file belonging to this implementation */ 2 | #include "taskFunctionsForSlave.hpp" 3 | 4 | /* External heaader files */ 5 | //... 6 | 7 | /* Project header files */ 8 | #include "globalVariables.hpp" 9 | #include "generalFunctions.hpp" 10 | #include "interactSD.hpp" 11 | #include "interactSPIFFS.hpp" 12 | #include "interactLoRa.hpp" 13 | #include "interactOLED.hpp" 14 | #include "interactWiFiSniffer.hpp" 15 | 16 | 17 | 18 | /* ------------------------ 19 | Task functions for Slave 20 | ------------------------ */ 21 | 22 | 23 | 24 | // Function definition for task running on core 0. 25 | // Monitors wifi packets while continuously changing channels. 26 | // Captured data is put on to the queue. 27 | void monitorWiFiDevices( void* parameter ) 28 | { 29 | //Serial.print( "monitorWiFiDevices runs on core: " ); 30 | //Serial.println( xPortGetCoreID() ); 31 | 32 | for(;;) 33 | { 34 | setWifiPromiscuousMode(); 35 | 36 | if (g_espConfigData.demo) 37 | { 38 | // (For demonstration purpose) Only detect devices in the specified channel of connected WiFi AP. Then the discovery of desired device is much faster! 39 | esp_wifi_set_channel( 2, WIFI_SECOND_CHAN_NONE ); 40 | 41 | vTaskDelay( 200 / portTICK_PERIOD_MS ); // stay on one channel for 200 ms to be sure not to miss any beancon message since according to WiFi standard a beacon message has to be sent every 100 msec. 42 | // See: https://en.wikipedia.org/wiki/Beacon_frame 43 | } 44 | else 45 | { 46 | for( size_t i = 0; i < maxCh; ++i ) 47 | { 48 | changeWifiChannel(); 49 | } 50 | } 51 | } 52 | 53 | } // monitorWiFiDevices 54 | 55 | 56 | // Function definition for task running on core 1. 57 | // This function logs the captured WiFi packet data to the SD card 58 | // and sends it to the master device for further processing/submission. 59 | void sendAndLogWiFiDeviceData( void* parameter ) 60 | { 61 | //Serial.print( "sendWiFiDeviceData runs on core: " ); 62 | //Serial.println( xPortGetCoreID() ); 63 | 64 | OLEDdisplayForLoRaSenderInit(); 65 | 66 | wifiDeviceData data; 67 | 68 | for(;;) 69 | { 70 | // Give some time for the scan to take palce. 71 | delay( 10 ); 72 | 73 | Serial.println(); 74 | Serial.println( F("New reading starts: ") ); 75 | Serial.println( F("------------------------------") ); 76 | 77 | { 78 | xQueueReceive( g_queueBetweenMonitorAndSend, &data, portMAX_DELAY ); 79 | 80 | #if defined(Slave_1) || defined(Slave_2) 81 | // Log data to binary file 82 | logToBinFileOnSD( data, "/logfile/binlog.dat" ); 83 | // Log data to CSV file 84 | logToCsvFileOnSD( data, "/logfile/logfile.csv" ); 85 | #endif 86 | 87 | sendToMasterDevice( data ); 88 | 89 | printWiFiDeviceData( data ); 90 | 91 | OLEDdisplayForLoRaSender( data ); 92 | 93 | delay( random(50) ); // Generate a random time interval for each slave sending packets, avoiding data collisions on reveiving side. 94 | } 95 | 96 | Serial.println( F("------------------------------") ); 97 | Serial.println( F("End of a round of receiving...") ); 98 | Serial.println(); 99 | } 100 | 101 | } // sendWiFiDeviceData -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/src/interactWiFi.cpp: -------------------------------------------------------------------------------- 1 | /* Header file belonging to this implementation */ 2 | #include "interactWiFi.hpp" 3 | 4 | /* External heaader files */ 5 | //... 6 | 7 | /* Project heaader files */ 8 | #include "interactOLED.hpp" 9 | 10 | 11 | int connectTime = 10; 12 | boolean connectStatus = false; 13 | 14 | 15 | // WiFi connection related helper functions 16 | 17 | String translateEncryptionType(wifi_auth_mode_t encryptionType) 18 | { 19 | switch (encryptionType) 20 | { 21 | case (WIFI_AUTH_OPEN): 22 | return "Open"; 23 | case (WIFI_AUTH_WEP): 24 | return "WEP"; 25 | case (WIFI_AUTH_WPA_PSK): 26 | return "WPA_PSK"; 27 | case (WIFI_AUTH_WPA2_PSK): 28 | return "WPA2_PSK"; 29 | case (WIFI_AUTH_WPA_WPA2_PSK): 30 | return "WPA_WPA2_PSK"; 31 | case (WIFI_AUTH_WPA2_ENTERPRISE): 32 | return "WPA2_ENTERPRISE"; 33 | default: 34 | return String(); 35 | } 36 | 37 | } // translateEncryptionType 38 | 39 | 40 | void scanWiFiNetworks() 41 | { 42 | Serial.println("WiFi scan start."); 43 | 44 | int numberOfNetworks = WiFi.scanNetworks(); 45 | 46 | Serial.println("WiFi Scan done."); 47 | 48 | if (numberOfNetworks == 0) 49 | { 50 | Serial.println("No networks found"); 51 | } 52 | else 53 | { 54 | Serial.println("\n\n"); 55 | Serial.print("Number of networks nearby found: "); 56 | Serial.println(numberOfNetworks); 57 | Serial.println("-----------------------"); 58 | 59 | for (int i = 0; i < numberOfNetworks; i++) 60 | { 61 | Serial.println(i + 1); 62 | Serial.print("Network name: "); 63 | Serial.println(WiFi.SSID(i)); 64 | 65 | Serial.print("Signal strength: "); 66 | Serial.println(WiFi.RSSI(i)); 67 | 68 | Serial.print("MAC address: "); 69 | Serial.println(WiFi.BSSIDstr(i)); 70 | 71 | Serial.print("Encryption type: "); 72 | String encryptionTypeDescription = translateEncryptionType(WiFi.encryptionType(i)); 73 | Serial.println(encryptionTypeDescription); 74 | Serial.println("-----------------------"); 75 | } 76 | 77 | Serial.println(""); 78 | delay(5000); 79 | } 80 | 81 | } // scanWiFiNetworks 82 | 83 | 84 | void connectToWiFi( const char* ssid, const char* password ) 85 | { 86 | // Connect to your WiFi modem 87 | WiFi.begin(ssid, password); 88 | 89 | pinMode(LEDPin, OUTPUT); 90 | 91 | Serial.println(); 92 | Serial.print( "Waiting for WiFi to connect..." ); 93 | OLEDdisplayForWiFiConnection(); 94 | 95 | // Check if successfully connected to WiFi network 96 | while ( WiFi.status() != WL_CONNECTED ) 97 | { 98 | delay(500); 99 | Serial.print("."); 100 | connectStatus = !connectStatus; 101 | if (connectStatus) 102 | { 103 | digitalWrite(LEDPin,HIGH); 104 | } 105 | else 106 | { 107 | digitalWrite(LEDPin,LOW); 108 | } 109 | 110 | if( (connectTime--) == 0) 111 | { 112 | Serial.println(); 113 | Serial.println( "ERROR - WiFi connection failed!" ); 114 | ESP.restart(); 115 | } 116 | } 117 | 118 | Serial.println(); 119 | Serial.println((String) "Successfully connecting to " + ssid + "!"); 120 | 121 | for (int i = 0; i < 5; i++) 122 | { 123 | digitalWrite(LEDPin,HIGH); 124 | delay(300); 125 | digitalWrite(LEDPin,LOW); 126 | delay(300); 127 | } 128 | 129 | delay(1000); 130 | digitalWrite(LEDPin,HIGH); 131 | delay(1000); 132 | 133 | } // connectToWiFiNetwork 134 | 135 | 136 | void disconnectWiFi() 137 | { 138 | WiFi.disconnect(true); 139 | WiFi.mode(WIFI_OFF); 140 | 141 | } // disconnectWiFi 142 | -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/include/globalVariables.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* External heaader files */ 4 | #include 5 | #include 6 | 7 | /* Project heaader files */ 8 | #include "dataTypes.hpp" 9 | 10 | 11 | /* ----------------------------------------------------------------------------------------- 12 | All global variables should be declared here. 13 | Their definition must be given in (only) one of the source files. 14 | 15 | Naming convention: 16 | A global variable should be indicated with the "g_" prefix. 17 | A constant global variable with the "cg_" prefix. 18 | 19 | Non-constant global variables (with "extern" in front to make them visible in other files) 20 | from here are defined mostly in "generalFunctions.cpp". 21 | ------------------------------------------------------------------------------------------ */ 22 | 23 | 24 | // Set pins for specific board 25 | #if defined(Master) || defined(Slave_1) || defined(Slave_2) 26 | // set OLED pins 27 | #define OLED_ADDRESS 0x3c 28 | #define OLED_SDA 21 29 | #define OLED_SCL 22 30 | #define LEDPin 25 31 | 32 | // set SD card pins 33 | #define SDCARD_SCK 14 34 | #define SDCARD_MISO 2 35 | #define SDCARD_MOSI 15 36 | #define SDCARD_CS 13 37 | 38 | // set LoRa pins 39 | #define LORA_SCK 5 40 | #define LORA_MISO 19 41 | #define LORA_MOSI 27 42 | #define LORA_SS 18 43 | #define LORA_RST 23 44 | #define LORA_DIO0 26 45 | #elif defined(Slave_4) 46 | // set OLED pins 47 | #define OLED_ADDRESS 0x3c 48 | #define OLED_SDA 4 49 | #define OLED_SCL 15 50 | #define OLED_RST 16 51 | #define LEDPin 25 52 | 53 | // set LoRa pins 54 | #define LORA_SCK 5 55 | #define LORA_MISO 19 56 | #define LORA_MOSI 27 57 | #define LORA_SS 18 58 | #define LORA_RST 14 59 | #define LORA_DIO0 26 60 | #elif defined(Slave_3) 61 | // set OLED pins 62 | #define OLED_ADDRESS 0x3c 63 | #define OLED_SDA 21 64 | #define OLED_SCL 22 65 | #define OLED_RST -1 66 | #define LEDPin 14 67 | 68 | // set LoRa pins 69 | #define LORA_SCK 5 70 | #define LORA_MISO 19 71 | #define LORA_MOSI 27 72 | #define LORA_SS 18 73 | #define LORA_RST 14 74 | #define LORA_DIO0 26 75 | #endif 76 | 77 | 78 | #if defined(Master) 79 | #define localAddress 0 80 | #elif defined(Slave_1) 81 | #define localAddress 1 82 | #define destinationAddress 0 83 | #elif defined(Slave_2) 84 | #define localAddress 2 85 | #define destinationAddress 0 86 | #elif defined(Slave_3) 87 | #define localAddress 3 88 | #define destinationAddress 0 89 | #elif defined(Slave_4) 90 | #define localAddress 4 91 | #define destinationAddress 0 92 | #endif 93 | 94 | 95 | // User-defined constants 96 | #define MAX_MACS_ON_SCREEN 5 97 | #define SCREEN_WIDTH 128 // OLED display width, in pixels 98 | #define SCREEN_HEIGHT 64 // OLED display height, in pixels  99 | #define maxCh 11 100 | #define HTTP_PORT 80 101 | 102 | 103 | extern QueueHandle_t g_queueBetweenMonitorAndSend; 104 | extern QueueHandle_t g_queueBetweenReceiveAndHandle; 105 | extern QueueHandle_t g_queueBetweenHandleAndWebServer; 106 | 107 | 108 | // Declare how many items should fit onto the queue 109 | const uint8_t cg_queueSizeMonitorAndSend = 100; 110 | const uint8_t cg_queueSizeBetweenReceiveAndHandle = 100; 111 | const uint8_t cg_queueSizeHandleAndWebServer = 100; 112 | 113 | 114 | // Current WiFi channel the ESP is listening to 115 | extern int g_curChannel; 116 | 117 | 118 | // Global variables for download request response 119 | //extern bool startToConvert; 120 | //extern uint32_t downloadRequestWithClientID; 121 | extern bool startToStreamData; 122 | extern uint32_t streamRequestWithClientID; 123 | 124 | 125 | // mDNS name 126 | const char mDNSname[] = "themostawesomesniffer"; 127 | 128 | 129 | // Global variables for reading in config data from JSON on SD / SPIFFS 130 | const char cg_path_to_config_JSON[] = "/server/config.js"; 131 | extern configData g_espConfigData; 132 | 133 | 134 | // Filter out "management" and "data" wifi packet types only 135 | const wifi_promiscuous_filter_t cg_filt = { 136 | .filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT | WIFI_PROMIS_FILTER_MASK_DATA 137 | }; 138 | -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/src/interactWebServer.cpp: -------------------------------------------------------------------------------- 1 | /* Header file belonging to this implementation */ 2 | #include "interactWebServer.hpp" 3 | 4 | /* External header files */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /* Project header files */ 12 | #include "globalVariables.hpp" 13 | #include "interactSD.hpp" 14 | 15 | 16 | // Main sources for understanding how the asynchronous websocket server works: 17 | // https://m1cr0lab-esp32.github.io/remote-control-with-websocket/ 18 | // https://github.com/me-no-dev/ESPAsyncWebServer 19 | 20 | 21 | // Define global variable ledState. 22 | bool ledState = 0; 23 | //bool startToConvert = false; 24 | //uint32_t downloadRequestWithClientID = 0; // IDs start from 1 only... 25 | bool startToStreamData = false; 26 | uint32_t streamRequestWithClientID = 0; 27 | int loggedDataCounter = 1; 28 | 29 | 30 | void notifyWebSocketClients( AsyncWebSocket* wsserver ) 31 | { 32 | wsserver->textAll( String(ledState) ); 33 | 34 | } // notifyWebSocketClients 35 | 36 | 37 | void handleWebSocketMessage( AsyncWebSocket* wsserver, AsyncWebSocketClient *wsclient, void *arg, uint8_t *data, size_t len) 38 | { 39 | AwsFrameInfo *info = (AwsFrameInfo*)arg; 40 | if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) 41 | { 42 | data[len] = 0; 43 | if (strcmp((char*)data, "toggle") == 0) 44 | { 45 | ledState = !ledState; 46 | notifyWebSocketClients( wsserver ); 47 | Serial.println( "LED status has been changed..." ); 48 | } 49 | else if (strcmp((char*)data, "streamData") == 0) 50 | { 51 | // If the Start/Stop button was pressed it enables/disables streaming 52 | // in the for cycle of the "hostWebserver" taskfunction by switching 53 | // the boolean value of "startToStreamData". 54 | Serial.println( "Streaming was started/halted." ); 55 | startToStreamData = !startToStreamData; 56 | streamRequestWithClientID = wsclient->id(); 57 | Serial.print( "This was the client id: " ); 58 | Serial.println( streamRequestWithClientID ); 59 | } 60 | } 61 | 62 | } // handleWebSocketMessage 63 | 64 | 65 | void handleWebSocketEvent( AsyncWebSocket *wsserver, AsyncWebSocketClient *wsclient, 66 | AwsEventType type, void *arg, uint8_t *data, size_t len ) 67 | { 68 | switch (type) 69 | { 70 | case WS_EVT_CONNECT: 71 | Serial.printf( "WebSocket client #%u connected from %s\n", 72 | wsclient->id(), wsclient->remoteIP().toString().c_str() ); 73 | break; 74 | case WS_EVT_DISCONNECT: 75 | Serial.printf( "WebSocket client #%u disconnected\n", wsclient->id() ); 76 | break; 77 | case WS_EVT_DATA: 78 | handleWebSocketMessage( wsserver, wsclient, arg, data, len ); 79 | break; 80 | case WS_EVT_PONG: 81 | case WS_EVT_ERROR: 82 | break; 83 | } 84 | 85 | } // handleWebSocketEvent 86 | 87 | 88 | void addWebSocket( AsyncWebSocket& wsserver, AsyncWebServer& server, AwsEventHandler wsHandler ) 89 | { 90 | wsserver.onEvent( wsHandler ); 91 | server.addHandler( &wsserver ); 92 | 93 | } // addWebSocket 94 | 95 | 96 | void cleanUpWebSocketClients( AsyncWebSocket& wsserver ) 97 | { 98 | wsserver.cleanupClients(); 99 | 100 | } // cleanUpWebSocketClients 101 | 102 | 103 | String templateProcessor( const String& var ) 104 | { 105 | if (var == "STATE") 106 | { 107 | if (ledState) 108 | { 109 | return "ON"; 110 | } 111 | else 112 | { 113 | return "OFF"; 114 | } 115 | } 116 | else 117 | { 118 | return "PROBLEM"; 119 | } 120 | 121 | } // templateProcessor 122 | 123 | 124 | void initWebServer( AsyncWebServer& server ) 125 | { 126 | // Route for root / web page 127 | // Arguments detailed here properly: https://techtutorialsx.com/2017/12/01/esp32-arduino-asynchronous-http-webserver/ 128 | // #1 path as string: URI. On which route the server should listen to requests? 129 | // #2 enum of type "WebRequestMethod": specifies the type of HTTP request. 130 | // #3 lambda function: 131 | server.on( "/", 132 | HTTP_GET, 133 | [](AsyncWebServerRequest *request) 134 | { request->send( SD, "/server/layout.htm", "text/html", false, templateProcessor ); } 135 | ); 136 | 137 | server.serveStatic( "/", SD, "/server/" ); 138 | 139 | // Start server 140 | server.begin(); 141 | 142 | } // initWebServer 143 | 144 | 145 | void addMDNS( const char* mDNSname ) 146 | { 147 | if (!MDNS.begin( mDNSname )) 148 | { 149 | Serial.println( F("Error starting mDNS...") ); 150 | for(;;); 151 | } 152 | 153 | } // addMDNS -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/include/dataTypes.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* External header files */ 4 | #include 5 | #include 6 | #include 7 | 8 | /* Project heaader files */ 9 | //... 10 | 11 | 12 | 13 | // For storing a MAC address. 14 | typedef struct 15 | { 16 | uint8_t mac[6]; 17 | 18 | } __attribute__((packed)) MacAddr; 19 | 20 | 21 | // Store mac header info (management type). Check this for the details: https://hackmag.com/security/esp32-sniffer/ 22 | typedef struct 23 | { 24 | int16_t fctl; // Frame control 25 | int16_t duration; // Duration 26 | MacAddr da; // Destination address 27 | MacAddr sa; // Source address 28 | MacAddr bssid; // Basic Service Set ID 29 | int16_t seqctl; // Sequence Control: Fragment Number and Sequence Number 30 | unsigned char payload[]; 31 | 32 | } __attribute__((packed)) WifiMgmtHdr; 33 | 34 | 35 | // Datatype for storing all the relevant data about a device captured by one esp32. 36 | typedef struct 37 | { 38 | uint32_t timestamp; // The local time when the packet is received. It is precise only if modem sleep or light sleep is not enabled. unit: microsecond 39 | // uint32_t is sufficient to store 1.19 hours of time uint64_t is enough for 5124095576 hours (but double the size of course...). 40 | MacAddr mac; // MAC address stands from 6 two digit hexadecimal numbers. 41 | // Each of it can be stored in an 8 bit variable. 42 | // The whole address therefore can be stored in a 6 long array of them. 43 | int8_t rssi; // Signal strength in dBm. 44 | 45 | } __attribute__((packed)) wifiDeviceData; 46 | // Because of "packed" the size will be 11 bytes no matter what, but since "timestamp" is exactly one word (32 bits on ESP32 as far as I know) and the 47 | // memory is 32 bit aligned it can be read in one cycle, while if it would be after "mac" or "rssi" it would get distributed over two 32 bit memory 48 | // chunks and therefore could be read only in to reading cycles. So this way it will be slightly faster. 49 | 50 | 51 | // Define maximum number of group member devices. Can be anything but needs to be predetermined. 52 | const int MAX_NUMBER_OF_SLAVES = 10; 53 | 54 | 55 | // Type for storing configuration data. 56 | typedef struct 57 | { 58 | char ssid[64]; // Max ssid length has to be changed if exceeds 64 character. 59 | char pswd[64]; // Max password length has to be changed if exceeds 64 character. 60 | MacAddr slave_MACs[MAX_NUMBER_OF_SLAVES]; // Slave device MACs. If more than 10 devices are used "MAX_NUMBER_OF_SLAVES" needs to be modified. 61 | float slave_x_coords[MAX_NUMBER_OF_SLAVES]; // x coordinates of the slave devices. Buffer for 10 values. See above. 62 | float slave_y_coords[MAX_NUMBER_OF_SLAVES]; // y coordinates of the slave devices. Buffer for 10 values. See slave device MACs above. 63 | float master_coords[2]; // x and y coordinate of the master device. 64 | MacAddr master_MAC; // MAC address of the master device. 65 | MacAddr photo_device_MAC; // MAC address of the device used at signal strength map creation. 66 | bool am_i_master; // A device is either master=1 or slave=0. 67 | bool demo; // Indicates whether it is a demo/validation run or a normal one. 68 | uint8_t number_of_slaves; // The number of slave devices in the group. Since "slave_MACs" won't be always completely filled, 69 | // the unfilled MAC spots will all be "00:00:00:00:00:00"/undefined, but they are not valid device MACs. 70 | int rssi_1m; // 1m RSSI. should be measured beforehand. 71 | float path_loss_exp; // Path loss exponent. Should be measured beforhand. 72 | 73 | } __attribute__((packed)) configData; 74 | 75 | 76 | // Type for storing the data collected from all slave devices. 77 | typedef struct 78 | { 79 | char timestamp[30]; // Timestamp in text based format in milliseconds (e.g., 2001-08-23 14:55:02.583) for the recordings were made. 80 | int8_t rssis[MAX_NUMBER_OF_SLAVES] = {0}; // For the RSSIs received from each slave device per datapacket. 81 | MacAddr mac; // For all of the MACs of the slave devices sending data to the master. 82 | 83 | } __attribute__((packed)) combinedWiFiDeviceData; 84 | 85 | 86 | // Type for storing the data collected from individual slave device. 87 | typedef struct 88 | { 89 | uint32_t packetID; 90 | int8_t rssi; 91 | int8_t slaveID; 92 | char mac[50] = {0}; // Make it large enough in case garbage were received by LoRa transmission. 93 | 94 | } __attribute__((packed)) wifiDevicePayload; -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/data/server/general.js: -------------------------------------------------------------------------------- 1 | var gateway = `ws://${window.location.hostname}/ws`; 2 | var websocket; 3 | 4 | var demo = undefined; // If true, it displays the demo_device_position on the map. 5 | var demo_device_position = [2.89, 1.92]; 6 | 7 | var RSSIc = undefined; //dBm 8 | var pathLossExp = undefined; 9 | 10 | var masterCoords = [undefined, undefined]; //m 11 | 12 | var numberOfSlaves = undefined; 13 | var espSlaveXcoords = []; //m 14 | var espSlaveYcoords = []; //m 15 | 16 | var roomContourXCoords = []; //m 17 | var roomContourYCoords = []; //m 18 | 19 | var roomMaxLength = undefined; // m 20 | var roomMaxWidth = undefined; // m 21 | var edgeGapSize = 0.05; // portion of canvas height/width 22 | 23 | 24 | 25 | window.addEventListener('load', onLoad); 26 | 27 | 28 | function onLoad(event) { 29 | parseConfigJSON(); 30 | parseRoomContourJSON(); 31 | initWebSocket(); 32 | initButtons(); 33 | // Display the Logs tab with measurement data as default: 34 | // by making an automated click on the tab button. 35 | document.getElementById("measTableTab").click(); 36 | 37 | } // onLoad 38 | 39 | 40 | function onOpen(event) { 41 | console.log('Connection opened'); 42 | 43 | } // onOpen 44 | 45 | 46 | function onClose(event) { 47 | console.log('Connection closed'); 48 | setTimeout(initWebSocket, 2000); 49 | 50 | } // onClose 51 | 52 | 53 | function onMessage(event) { 54 | var state; 55 | if (event.data == "1") { 56 | state = "ON"; 57 | document.getElementById('ledState').innerHTML = state; 58 | } else if (event.data == "0") { 59 | state = "OFF"; 60 | document.getElementById('ledState').innerHTML = state; 61 | } else if (event.data == "stream started") { 62 | console.log("Stream started."); 63 | } else { 64 | let data = JSON.parse(event.data); 65 | appendDataToLogsTable(data); 66 | 67 | console.log(data.time); 68 | console.log(data.MAC); 69 | 70 | appendPosToPosTable(data); 71 | } 72 | 73 | } // onMessage 74 | 75 | 76 | function initWebSocket() { 77 | console.log('Trying to open a WebSocket connection...'); 78 | websocket = new WebSocket(gateway); 79 | websocket.onopen = onOpen; 80 | websocket.onclose = onClose; 81 | websocket.onmessage = onMessage; 82 | 83 | } // initWebSocket 84 | 85 | 86 | function initButtons() { 87 | document.getElementById('ledButton').addEventListener('click', toggle); 88 | 89 | } // initButtons 90 | 91 | 92 | function toggle() { 93 | websocket.send('toggle'); 94 | 95 | } // toggle 96 | 97 | 98 | function dataStreamStartOrStop() { 99 | var elem = document.getElementById("startButton"); 100 | if (elem.value == "Start") { 101 | elem.value = "Stop"; 102 | } else { 103 | elem.value = "Start"; 104 | } 105 | websocket.send('streamData'); 106 | 107 | } // dataStreamStartOrStop 108 | 109 | 110 | function loadRoomContourJSON(callback) { 111 | var xobj = new XMLHttpRequest(); 112 | xobj.open('GET', 'room.json', true); 113 | xobj.onreadystatechange = function() { 114 | if (xobj.readyState == 4 && xobj.status == "200") { 115 | callback(xobj.responseText); 116 | } 117 | }; 118 | xobj.send(null); 119 | 120 | } // loadRoomContourJSON 121 | 122 | 123 | function parseRoomContourJSON() { 124 | var roomContourJson; 125 | 126 | function callbackfunc(response) { 127 | 128 | roomContourJson = JSON.parse(response); 129 | 130 | var roomContourCoords = roomContourJson.room_contour; 131 | 132 | for (var i = 0; i < roomContourCoords.length; i++) { 133 | roomContourXCoords[i] = roomContourJson.room_contour[i].x; 134 | roomContourYCoords[i] = roomContourJson.room_contour[i].y; 135 | } 136 | 137 | console.log(Math.max(...roomContourXCoords)); 138 | roomMaxLength = Math.max(...roomContourXCoords); 139 | roomMaxWidth = Math.max(...roomContourYCoords); 140 | 141 | } 142 | 143 | loadRoomContourJSON(callbackfunc); 144 | 145 | } // parseRoomContourJSON 146 | 147 | 148 | function loadConfigJSON(callback) { 149 | var xobj = new XMLHttpRequest(); 150 | xobj.overrideMimeType("application/json"); 151 | xobj.open('GET', 'config.json', true); 152 | xobj.onreadystatechange = function() { 153 | if (xobj.readyState == 4 && xobj.status == "200") { 154 | callback(xobj.responseText); 155 | } 156 | }; 157 | xobj.send(null); 158 | 159 | } // loadConfigJSON 160 | 161 | 162 | function parseConfigJSON() { 163 | var configJson; 164 | 165 | function callbackfunc(response) { 166 | 167 | configJson = JSON.parse(response); 168 | 169 | demo = configJson.demo; 170 | 171 | pathLossExp = configJson.path_loss_exp; 172 | RSSIc = configJson.RSSI_1m; 173 | 174 | masterCoords[0] = configJson.master_coords.x; 175 | masterCoords[1] = configJson.master_coords.y; 176 | 177 | numberOfSlaves = configJson.number_of_slaves; 178 | 179 | for (var i = 0; i < numberOfSlaves; i++) { 180 | espSlaveXcoords[i] = configJson.slave_coords[i].x; 181 | espSlaveYcoords[i] = configJson.slave_coords[i].y; 182 | } 183 | 184 | } 185 | 186 | loadConfigJSON(callbackfunc); 187 | 188 | } // parseConfigJSON -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/sdcardcontent/server/general.js: -------------------------------------------------------------------------------- 1 | var gateway = `ws://${window.location.hostname}/ws`; 2 | var websocket; 3 | 4 | var demo = undefined; // If true, it displays the demo_device_position on the map. 5 | var demo_device_position = [2.89, 1.92]; 6 | 7 | var RSSIc = undefined; //dBm 8 | var pathLossExp = undefined; 9 | 10 | var masterCoords = [undefined, undefined]; //m 11 | 12 | var numberOfSlaves = undefined; 13 | var espSlaveXcoords = []; //m 14 | var espSlaveYcoords = []; //m 15 | 16 | var roomContourXCoords = []; //m 17 | var roomContourYCoords = []; //m 18 | 19 | var roomMaxLength = undefined; // m 20 | var roomMaxWidth = undefined; // m 21 | var edgeGapSize = 0.05; // portion of canvas height/width 22 | 23 | 24 | 25 | window.addEventListener('load', onLoad); 26 | 27 | 28 | function onLoad(event) { 29 | parseConfigJSON(); 30 | parseRoomContourJSON(); 31 | initWebSocket(); 32 | initButtons(); 33 | // Display the Logs tab with measurement data as default: 34 | // by making an automated click on the tab button. 35 | document.getElementById("measTableTab").click(); 36 | 37 | } // onLoad 38 | 39 | 40 | function onOpen(event) { 41 | console.log('Connection opened'); 42 | 43 | } // onOpen 44 | 45 | 46 | function onClose(event) { 47 | console.log('Connection closed'); 48 | setTimeout(initWebSocket, 2000); 49 | 50 | } // onClose 51 | 52 | 53 | function onMessage(event) { 54 | var state; 55 | if (event.data == "1") { 56 | state = "ON"; 57 | document.getElementById('ledState').innerHTML = state; 58 | } else if (event.data == "0") { 59 | state = "OFF"; 60 | document.getElementById('ledState').innerHTML = state; 61 | } else if (event.data == "stream started") { 62 | console.log("Stream started."); 63 | } else { 64 | let data = JSON.parse(event.data); 65 | appendDataToLogsTable(data); 66 | 67 | console.log(data.time); 68 | console.log(data.MAC); 69 | 70 | appendPosToPosTable(data); 71 | } 72 | 73 | } // onMessage 74 | 75 | 76 | function initWebSocket() { 77 | console.log('Trying to open a WebSocket connection...'); 78 | websocket = new WebSocket(gateway); 79 | websocket.onopen = onOpen; 80 | websocket.onclose = onClose; 81 | websocket.onmessage = onMessage; 82 | 83 | } // initWebSocket 84 | 85 | 86 | function initButtons() { 87 | document.getElementById('ledButton').addEventListener('click', toggle); 88 | 89 | } // initButtons 90 | 91 | 92 | function toggle() { 93 | websocket.send('toggle'); 94 | 95 | } // toggle 96 | 97 | 98 | function dataStreamStartOrStop() { 99 | var elem = document.getElementById("startButton"); 100 | if (elem.value == "Start") { 101 | elem.value = "Stop"; 102 | } else { 103 | elem.value = "Start"; 104 | } 105 | websocket.send('streamData'); 106 | 107 | } // dataStreamStartOrStop 108 | 109 | 110 | function loadRoomContourJSON(callback) { 111 | var xobj = new XMLHttpRequest(); 112 | xobj.open('GET', 'room.json', true); 113 | xobj.onreadystatechange = function() { 114 | if (xobj.readyState == 4 && xobj.status == "200") { 115 | callback(xobj.responseText); 116 | } 117 | }; 118 | xobj.send(null); 119 | 120 | } // loadRoomContourJSON 121 | 122 | 123 | function parseRoomContourJSON() { 124 | var roomContourJson; 125 | 126 | function callbackfunc(response) { 127 | 128 | roomContourJson = JSON.parse(response); 129 | 130 | var roomContourCoords = roomContourJson.room_contour; 131 | 132 | for (var i = 0; i < roomContourCoords.length; i++) { 133 | roomContourXCoords[i] = roomContourJson.room_contour[i].x; 134 | roomContourYCoords[i] = roomContourJson.room_contour[i].y; 135 | } 136 | 137 | console.log(Math.max(...roomContourXCoords)); 138 | roomMaxLength = Math.max(...roomContourXCoords); 139 | roomMaxWidth = Math.max(...roomContourYCoords); 140 | 141 | } 142 | 143 | loadRoomContourJSON(callbackfunc); 144 | 145 | } // parseRoomContourJSON 146 | 147 | 148 | function loadConfigJSON(callback) { 149 | var xobj = new XMLHttpRequest(); 150 | xobj.overrideMimeType("application/json"); 151 | xobj.open('GET', 'config.json', true); 152 | xobj.onreadystatechange = function() { 153 | if (xobj.readyState == 4 && xobj.status == "200") { 154 | callback(xobj.responseText); 155 | } 156 | }; 157 | xobj.send(null); 158 | 159 | } // loadConfigJSON 160 | 161 | 162 | function parseConfigJSON() { 163 | var configJson; 164 | 165 | function callbackfunc(response) { 166 | 167 | configJson = JSON.parse(response); 168 | 169 | demo = configJson.demo; 170 | 171 | pathLossExp = configJson.path_loss_exp; 172 | RSSIc = configJson.RSSI_1m; 173 | 174 | masterCoords[0] = configJson.master_coords.x; 175 | masterCoords[1] = configJson.master_coords.y; 176 | 177 | numberOfSlaves = configJson.number_of_slaves; 178 | 179 | for (var i = 0; i < numberOfSlaves; i++) { 180 | espSlaveXcoords[i] = configJson.slave_coords[i].x; 181 | espSlaveYcoords[i] = configJson.slave_coords[i].y; 182 | } 183 | 184 | } 185 | 186 | loadConfigJSON(callbackfunc); 187 | 188 | } // parseConfigJSON -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/src/interactLoRa.cpp: -------------------------------------------------------------------------------- 1 | /* Header file belonging to this implementation */ 2 | #include "interactLoRa.hpp" 3 | 4 | /* External heaader files */ 5 | //... 6 | 7 | /* Project header files */ 8 | #include "globalVariables.hpp" 9 | #include "generalFunctions.hpp" 10 | #include "interactOLED.hpp" 11 | #include "interactWiFiSniffer.hpp" 12 | 13 | 14 | 15 | unsigned int packetCounter1 = 0; 16 | unsigned int packetCounter2 = 0; 17 | unsigned int packetCounter3 = 0; 18 | unsigned int packetCounter4 = 0; 19 | 20 | 21 | 22 | void initializeLoRa() 23 | { 24 | Serial.println(); 25 | Serial.println( "Initializing LoRa radio..." ); 26 | 27 | // setup LoRa transceiver module 28 | SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_SS); 29 | LoRa.setPins(LORA_SS, LORA_RST, LORA_DIO0); 30 | 31 | // replace the LoRa.begin(---E-) argument with your location's frequency 32 | // 433E6 for Asia 33 | // 866E6 for Europe 34 | // 915E6 for North America 35 | int counter = 0; 36 | while ( !LoRa.begin(866E6) && counter < 10 ) 37 | { 38 | Serial.print("."); 39 | counter++; 40 | delay(500); 41 | } 42 | if (counter == 10) 43 | { 44 | Serial.println( "ERROR - LoRa initialization failed." ); 45 | } 46 | 47 | Serial.println( "LoRa initialization OK!" ); 48 | OLEDdisplayForLoRaInit(); 49 | 50 | LoRa.setSignalBandwidth(250E3); 51 | 52 | } // initializeLoRa 53 | 54 | 55 | void sendToMasterDevice( wifiDeviceData data ) 56 | { 57 | String payload = ""; 58 | 59 | #if defined(Slave_1) 60 | payload += String(packetCounter1) + "/"; // add packet ID 61 | #elif defined(Slave_2) 62 | payload += String(packetCounter2) + "/"; 63 | #elif defined(Slave_3) 64 | payload += String(packetCounter3) + "/"; 65 | #elif defined(Slave_4) 66 | payload += String(packetCounter4) + "/"; 67 | #endif 68 | 69 | char strMacAddr[18] = {0}; 70 | MACnumberTostring( strMacAddr, data.mac ); 71 | payload += String(strMacAddr) + "\t"; // add WiFi device payload - MAC address 72 | 73 | payload += String(data.rssi) + "\n"; // add WiFi device payload - RSSI 74 | 75 | 76 | #if defined(Slave_1) 77 | Serial.print( F("Slave #1 ") ); 78 | #elif defined(Slave_2) 79 | Serial.print( F("Slave #2 ") ); 80 | #elif defined(Slave_3) 81 | Serial.print( F("Slave #3 ") ); 82 | #elif defined(Slave_4) 83 | Serial.print( F("Slave #4 ") ); 84 | #endif 85 | 86 | Serial.print( F("is sending packet: ") ); 87 | 88 | #if defined(Slave_1) 89 | Serial.println( packetCounter1 ); 90 | #elif defined(Slave_2) 91 | Serial.println( packetCounter2 ); 92 | #elif defined(Slave_3) 93 | Serial.println( packetCounter3 ); 94 | #elif defined(Slave_4) 95 | Serial.println( packetCounter4 ); 96 | #endif 97 | 98 | LoRa.idle(); // put the radio in idle (standby) mode 99 | LoRa.beginPacket(); // start packet 100 | 101 | #if defined(Slave_1) || defined(Slave_2) || defined(Slave_3) || defined(Slave_4) 102 | LoRa.write(destinationAddress); // add destination address 103 | #endif 104 | 105 | LoRa.write(localAddress); // add local address 106 | 107 | LoRa.print(payload); // add WiFi device payload (packetID + '/' + MAC address + '\t' + RSSI + '\n') 108 | 109 | LoRa.endPacket(); // finish packet and send it 110 | LoRa.sleep(); // put the radio in sleep mode. 111 | 112 | Serial.println( F("Packet sent.") ); 113 | 114 | } // sendToMasterDevice 115 | 116 | 117 | void receiveFromSlaveDevice( int packetSize ) 118 | { 119 | if ( !packetSize ) 120 | { 121 | return; 122 | } 123 | else 124 | { 125 | wifiDevicePayload WiFiDevicePayload; 126 | 127 | // read packet 128 | int recipient = LoRa.read(); 129 | int sender = LoRa.read(); 130 | 131 | String incomingPayload = ""; // payload of incoming WiFi device packet 132 | while ( LoRa.available() ) 133 | { 134 | incomingPayload = LoRa.readString(); 135 | // incomingPayload format: packetID + '/' + MAC address + '\t' + RSSI + '\n' 136 | //Serial.println( incomingPayload ); 137 | } 138 | 139 | if ( recipient != localAddress ) 140 | { 141 | //Serial.println( "ERROR - Recipient address does not match local address!" ); 142 | return; 143 | } 144 | 145 | WiFiDevicePayload.slaveID = sender; 146 | 147 | int pos1 = incomingPayload.indexOf('/'); 148 | int pos2 = incomingPayload.indexOf('\t', pos1); 149 | int pos3 = incomingPayload.indexOf('\n', pos2); 150 | 151 | WiFiDevicePayload.packetID = (uint32_t) strtoul( incomingPayload.substring(0, pos1).c_str(), NULL, 0 ); // packetID 152 | strcpy( WiFiDevicePayload.mac, incomingPayload.substring(pos1 + 1, pos2).c_str() ); // MAC Address 153 | WiFiDevicePayload.rssi = (int8_t) incomingPayload.substring(pos2 + 1, pos3).toInt(); // RSSI 154 | 155 | // display payload information 156 | //printWiFiDevicePayload( WiFiDevicePayload ); 157 | 158 | // display packet information on OLED display 159 | //OLEDdisplayForLoRaReceiver( sender ); 160 | 161 | if (strlen(WiFiDevicePayload.mac) == 17) // check if WiFiDevicePayload.mac contains garbages (must only contain "XX:XX:XX:XX:XX:XX") 162 | { 163 | xQueueSend( g_queueBetweenReceiveAndHandle, &WiFiDevicePayload, 0 ); 164 | } 165 | } 166 | 167 | } // receiveFromSlaveDevice -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/src/interactNTP.cpp: -------------------------------------------------------------------------------- 1 | /* Header file belonging to this implementation */ 2 | #include "interactNTP.hpp" 3 | 4 | /* External heaader files */ 5 | //... 6 | 7 | /* Project heaader files */ 8 | #include "generalFunctions.hpp" 9 | 10 | 11 | const char* NTP_SERVER = "pool.ntp.org"; 12 | const char* TZ_INFO = "CET-1CEST,M3.5.0,M10.5.0/3"; // Time zone in Berlin, Germany 13 | // See https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv for timezone codes for your region 14 | 15 | 16 | struct tm timeinfo; 17 | struct timeval tv; 18 | // Unix Time in seconds 19 | time_t now; 20 | // Unix Time in milliseconds 21 | uint64_t time_ms; 22 | // Unix Time in microseconds 23 | uint64_t time_us; 24 | // Millisecond fraction 25 | uint64_t ms_frac; 26 | // Microsecond fraction 27 | uint64_t us_frac; 28 | 29 | 30 | bool callNTPServer(int sec) 31 | { 32 | { 33 | uint32_t start = millis(); 34 | 35 | // Get the time 36 | //Serial.print( F("Waiting for system time to be set...") ); 37 | do 38 | { 39 | time(&now); 40 | localtime_r(&now, &timeinfo); // update fields in the strcuture tm 41 | if ( gettimeofday(&tv, NULL) != 0 ) // gettimeofday() return -1 for failure, 0 for success 42 | { 43 | Serial.println( F("ERROR - Failed to obtain time!") ); 44 | ESP.restart(); 45 | return false; 46 | } 47 | delay(10); 48 | } while (((millis() - start) <= (1000 * sec)) && (timeinfo.tm_year < (2016 - 1900))); 49 | 50 | if (timeinfo.tm_year <= (2016 - 1900)) 51 | { 52 | Serial.println( F("ERROR - Failed to obtain time!") ); 53 | ESP.restart(); 54 | return false; 55 | } 56 | } 57 | 58 | return true; 59 | 60 | } // callNTPServer 61 | 62 | 63 | uint64_t getUnixTimeinMilliseconds() // e.g., 1610279529468 64 | { 65 | if ( callNTPServer(10) ) // wait up to 10 sec to sync 66 | { 67 | Serial.print( F("NTP Server got called: ") ); 68 | char time_output[30]; 69 | getTextFormatTime(time_output); 70 | Serial.println(time_output); 71 | 72 | time_ms = (uint64_t) tv.tv_sec * 1000LL + (uint64_t) tv.tv_usec / 1000LL; 73 | return time_ms; 74 | } 75 | 76 | return (uint64_t) 0; // failed to synchronize time 77 | 78 | } // getUnixTimeinMilliseconds 79 | 80 | 81 | uint64_t getUnixTimeinMicroseconds() // e.g., 1610279529468548 82 | { 83 | if ( callNTPServer(10) ) // wait up to 10 sec to sync 84 | { 85 | Serial.print( F("NTP Server got called: ") ); 86 | char time_output[30]; 87 | getTextFormatTime(time_output); 88 | Serial.println(time_output); 89 | 90 | time_us = (uint64_t) tv.tv_sec * 1000000L + (uint64_t) tv.tv_usec; 91 | return time_us; 92 | } 93 | 94 | return (uint64_t) 0; // failed to synchronize time 95 | 96 | } // getUnixTimeinMicroseconds 97 | 98 | 99 | void getTextFormatTime(char *buf) // e.g., 2001-08-23 14:55:02 100 | { 101 | if ( callNTPServer(10) ) // wait up to 10 sec to sync 102 | { 103 | strftime(buf, 30, "%F %T", localtime(&now)); // For more format specifiers of the corresponding values to represent time, 104 | // See: http://www.cplusplus.com/reference/ctime/strftime/ 105 | } 106 | 107 | } // getTextFormatTime 108 | 109 | 110 | void getTextFormatTimeinMilliseconds(char *buf) // e.g., 2001-08-23 14:55:02.583 111 | { 112 | if ( callNTPServer(10) ) // wait up to 10 sec to sync 113 | { 114 | strftime(buf, 20, "%F %T", localtime(&now)); // For more format specifiers of the corresponding values to represent time, 115 | // See: http://www.cplusplus.com/reference/ctime/strftime/ 116 | } 117 | 118 | strcat( buf, "." ); 119 | 120 | ms_frac = (uint64_t) tv.tv_usec / 1000LL; 121 | char arr_ms_frac[4] = {0}; 122 | strcpy( arr_ms_frac, uint64_to_string( ms_frac ).c_str() ); 123 | strcat( buf, arr_ms_frac ); 124 | 125 | } // getTextFormatTimeinMilliseconds 126 | 127 | 128 | void getTextFormatTimeinMicroseconds(char *buf) // e.g., 2001-08-23 14:55:02.583546 129 | { 130 | if ( callNTPServer(10) ) // wait up to 10 sec to sync 131 | { 132 | strftime(buf, 20, "%F %T", localtime(&now)); // For more format specifiers of the corresponding values to represent time, 133 | // See: http://www.cplusplus.com/reference/ctime/strftime/ 134 | } 135 | 136 | strcat( buf, "." ); 137 | 138 | us_frac = (uint64_t) tv.tv_usec; 139 | char arr_us_frac[4] = {0}; 140 | strcpy( arr_us_frac, uint64_to_string( us_frac ).c_str() ); 141 | strcat( buf, arr_us_frac ); 142 | 143 | } // getTextFormatTimeinMicroseconds 144 | 145 | 146 | void printLocalTime(tm localTime) 147 | { 148 | if ( callNTPServer(10) ) // wait up to 10 sec to sync 149 | { 150 | Serial.print("Now: "); 151 | char time_output[30]; 152 | getTextFormatTime(time_output); 153 | Serial.println(time_output); 154 | 155 | // Unix Time in sec, msec, usec 156 | Serial.println(); 157 | 158 | Serial.println("Unix Time: "); 159 | Serial.print("TimeVal-sec = "); 160 | Serial.println(now); 161 | 162 | Serial.print("TimeVal-msec = "); 163 | time_ms = (uint64_t) tv.tv_sec * 1000LL + (uint64_t) tv.tv_usec / 1000LL; 164 | print_uint64_t(time_ms); 165 | Serial.println(); 166 | 167 | Serial.print("TimeVal-usec = "); 168 | time_us = (uint64_t) tv.tv_sec * 1000000L + (uint64_t) tv.tv_usec; 169 | print_uint64_t(time_us); 170 | 171 | Serial.println("\n\n"); 172 | } 173 | 174 | } // printLocalTime -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/src/interactSPIFFS.cpp: -------------------------------------------------------------------------------- 1 | #if defined(Slave_3) || defined(Slave_4) 2 | 3 | 4 | /* Header file belonging to this implementation */ 5 | #include "interactSPIFFS.hpp" 6 | 7 | /* External heaader files */ 8 | //... 9 | 10 | /* Project header files */ 11 | #include "interactOLED.hpp" 12 | 13 | 14 | void initializeSIPFFS() 15 | { 16 | // clean FS, for testing purpose 17 | //SPIFFS.format(); 18 | 19 | Serial.println(); 20 | Serial.println( "Mounting SPIFFS..." ); 21 | 22 | if ( SPIFFS.begin() ) 23 | { 24 | Serial.println( "Successfully mount file system!" ); 25 | OLEDdisplayForSPIFFSInit(); 26 | } 27 | else 28 | { 29 | Serial.println( "ERROR - failed to mount SPIFFS!" ); 30 | for(;;); // if failed, do nothing. 31 | } 32 | 33 | } // initializeSIPFFS 34 | 35 | 36 | void listDir(fs::FS &fs, const char * dirname, uint8_t levels) 37 | { 38 | Serial.printf("Listing directory: %s\r\n", dirname); 39 | 40 | File root = fs.open(dirname); 41 | if (!root) { 42 | Serial.println("- failed to open directory"); 43 | return; 44 | } 45 | if (!root.isDirectory()) { 46 | Serial.println(" - not a directory"); 47 | return; 48 | } 49 | 50 | File file = root.openNextFile(); 51 | while(file) { 52 | if (file.isDirectory()){ 53 | Serial.print(" DIR : "); 54 | Serial.println(file.name()); 55 | if(levels){ 56 | listDir(fs, file.name(), levels -1); 57 | } 58 | } else { 59 | Serial.print(" FILE: "); 60 | Serial.print(file.name()); 61 | Serial.print("\tSIZE: "); 62 | Serial.println(file.size()); 63 | } 64 | file = root.openNextFile(); 65 | } 66 | 67 | } // listDir 68 | 69 | 70 | void readFile(fs::FS &fs, const char * path) 71 | { 72 | Serial.printf("Reading file: %s\r\n", path); 73 | 74 | File file = fs.open(path); 75 | if(!file || file.isDirectory()){ 76 | Serial.println("- failed to open file for reading"); 77 | return; 78 | } 79 | 80 | Serial.println("- read from file:"); 81 | while(file.available()){ 82 | Serial.write(file.read()); 83 | } 84 | 85 | file.close(); 86 | 87 | } // readFile 88 | 89 | 90 | void writeFile(fs::FS &fs, const char * path, const char * message) 91 | { 92 | Serial.printf( "Writing file: %s\n", path ); 93 | 94 | File file = fs.open(path, FILE_WRITE); 95 | if(!file) { 96 | Serial.println( "Failed to open file for writing." ); 97 | return; 98 | } 99 | if(file.print(message)) 100 | { 101 | Serial.println( "File written." ); 102 | } 103 | else 104 | { 105 | Serial.println( "Write failed." ); 106 | } 107 | 108 | file.close(); 109 | 110 | } // writeFile 111 | 112 | 113 | void appendFile(fs::FS &fs, const char * path, const char * message) 114 | { 115 | // Serial.printf("Appending to file: %s\n", path); 116 | File file = fs.open(path, FILE_APPEND); 117 | if (!file) { 118 | Serial.println("Failed to open file for appending."); 119 | return; 120 | } 121 | 122 | if (file.print(message)) { 123 | //Serial.println("Message appended"); 124 | } else { 125 | Serial.println("Append failed."); 126 | } 127 | 128 | file.close(); 129 | 130 | } // appendFile 131 | 132 | 133 | void logSPISSF(std::string dataMessage, const char* fileToLogTo) 134 | { 135 | appendFile( SPIFFS, fileToLogTo, dataMessage.c_str() ); 136 | 137 | } // logSPISSF 138 | 139 | 140 | void logSPIFFSFileHeader( std::string headerText ) 141 | { 142 | writeFile( SPIFFS, "/WiFiDeviceData.txt", headerText.c_str() ); 143 | 144 | } // logSPIFFSFileHeader 145 | 146 | 147 | void renameFile(fs::FS &fs, const char * path1, const char * path2) 148 | { 149 | Serial.printf("Renaming file %s to %s\r\n", path1, path2); 150 | if (fs.rename(path1, path2)) 151 | { 152 | Serial.println("- file renamed"); 153 | } 154 | else 155 | { 156 | Serial.println("- rename failed"); 157 | } 158 | 159 | } // renameFile 160 | 161 | 162 | void deleteFile(fs::FS &fs, const char * path) 163 | { 164 | Serial.printf("Deleting file: %s\r\n", path); 165 | if(fs.remove(path)) 166 | { 167 | Serial.println("- file deleted"); 168 | } 169 | else 170 | { 171 | Serial.println("- delete failed"); 172 | } 173 | 174 | } // deleteFile 175 | 176 | 177 | void testFileIO(fs::FS &fs, const char * path) 178 | { 179 | Serial.printf("Testing file I/O with %s\r\n", path); 180 | 181 | static uint8_t buf[512]; 182 | size_t len = 0; 183 | File file = fs.open(path, FILE_WRITE); 184 | if(!file) 185 | { 186 | Serial.println("- failed to open file for writing"); 187 | return; 188 | } 189 | 190 | size_t i; 191 | Serial.print("- writing" ); 192 | uint32_t start = millis(); 193 | for (size_t i = 0; i < 2048; i++) 194 | { 195 | if ((i & 0x001F) == 0x001F) 196 | { 197 | Serial.print("."); 198 | } 199 | file.write(buf, 512); 200 | } 201 | Serial.println(""); 202 | uint32_t end = millis() - start; 203 | Serial.printf(" - %u bytes written in %u ms\r\n", 2048 * 512, end); 204 | file.close(); 205 | 206 | file = fs.open(path); 207 | start = millis(); 208 | end = start; 209 | i = 0; 210 | if (file && !file.isDirectory()) 211 | { 212 | len = file.size(); 213 | size_t flen = len; 214 | start = millis(); 215 | Serial.print("- reading" ); 216 | while(len) 217 | { 218 | size_t toRead = len; 219 | if(toRead > 512) 220 | { 221 | toRead = 512; 222 | } 223 | file.read(buf, toRead); 224 | if ((i++ & 0x001F) == 0x001F) 225 | { 226 | Serial.print("."); 227 | } 228 | len -= toRead; 229 | } 230 | Serial.println(""); 231 | end = millis() - start; 232 | Serial.printf("- %u bytes read in %u ms\r\n", flen, end); 233 | file.close(); 234 | } 235 | else 236 | { 237 | Serial.println("- failed to open file for reading"); 238 | } 239 | 240 | } // testFileIO 241 | 242 | 243 | #endif -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/src/taskFunctionsForMaster.cpp: -------------------------------------------------------------------------------- 1 | /* Header file belonging to this implementation */ 2 | #include "taskFunctionsForMaster.hpp" 3 | 4 | /* External header files */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /* Project header files */ 11 | #include "globalVariables.hpp" 12 | #include "generalFunctions.hpp" 13 | #include "interactData.hpp" 14 | #include "interactSD.hpp" 15 | #include "interactSPIFFS.hpp" 16 | #include "interactLoRa.hpp" 17 | #include "interactOLED.hpp" 18 | #include "interactNTP.hpp" 19 | #include "interactWiFi.hpp" 20 | #include "interactWiFiSniffer.hpp" 21 | #include "interactWebServer.hpp" 22 | 23 | 24 | 25 | /* ------------------------- 26 | Task functions for Master 27 | ------------------------- */ 28 | 29 | 30 | 31 | // Function definition for task on core 0, receiving data from slave ESPs. 32 | void receiveAndLogWiFiDeviceData( void* parameter ) 33 | { 34 | //Serial.print( "receiveAndLogWiFiDeviceData runs on core: " ); 35 | //Serial.println( xPortGetCoreID() ); 36 | 37 | LoRa.receive(); // puts the radio in continuous receive mode 38 | 39 | // Connect to WiFi network 40 | connectToWiFi( g_espConfigData.ssid, g_espConfigData.pswd ); 41 | { 42 | Serial.print( F("Access web server at: ") ); 43 | Serial.println( WiFi.localIP() ); 44 | 45 | OLEDdisplayForIPAddress(); 46 | } 47 | 48 | // Initialize NTP client 49 | Serial.println(); 50 | Serial.println( F("Initializing NTP...") ); 51 | configTime( 0, 0, NTP_SERVER ); // Syntax: configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); 52 | setenv( "TZ", TZ_INFO, 1 ); // Set time zone 53 | printLocalTime( timeinfo ); 54 | 55 | { 56 | Serial.println( F("Waiting for data from slaves...\n\n") ); 57 | OLEDdisplayForMasterInit(); 58 | } 59 | 60 | int8_t rawRSSI[g_espConfigData.number_of_slaves] = {0}; // for demo mode 61 | std::vector buffer; // for normal mode 62 | 63 | wifiDevicePayload payload; 64 | 65 | for(;;) 66 | { 67 | /* // For dummy data generator generateRecordAtPointLNSMmodelFixedMac(). Use this block when no (or not enough) slave devices are available to measure real data. 68 | { 69 | // Moving on line 70 | int RSSIc = g_espConfigData.rssi_1m; // RSSI value at 1 m. 71 | float pathLossExp = g_espConfigData.path_loss_exp; // path loss exponent measured for esp in room, free-line-of-sight. 72 | uint8_t numOfMeasPoints = (4.58 - 0.02) / 0.02; 73 | float slope_of_line = (3.00 - 0.02) / (4.58 - 0.02); 74 | float x_coord = 0.02 * random( 1, numOfMeasPoints ); 75 | float y_coord = slope_of_line * x_coord; 76 | 77 | combinedWiFiDeviceData combinedData = generateRecordAtPointLNSMmodelFixedMac( x_coord, y_coord, RSSIc, pathLossExp ); 78 | 79 | Serial.print( combinedData.timestamp ); 80 | Serial.print( ": " ); 81 | Serial.print( x_coord ); 82 | Serial.print( " " ); 83 | Serial.print( y_coord ); 84 | Serial.print( "\n" ); 85 | 86 | xQueueSend( g_queueBetweenHandleAndWebServer, &combinedData, 0); 87 | } */ 88 | 89 | { 90 | receiveFromSlaveDevice( LoRa.parsePacket() ); 91 | 92 | xQueueReceive( g_queueBetweenReceiveAndHandle, &payload, 1 ); 93 | //printWiFiDevicePayload( payload ); 94 | 95 | if (g_espConfigData.demo) 96 | { 97 | demoModeDataProcessor( payload, rawRSSI ); 98 | } 99 | else 100 | { 101 | normalModeDataProcessor( payload, buffer ); 102 | } 103 | } 104 | 105 | vTaskDelay( 1 / portTICK_PERIOD_MS ); 106 | } 107 | 108 | } // receiveAndLogWiFiDeviceData 109 | 110 | 111 | // Function definition for task on core 1, hosting webserwer. 112 | void hostWebServer( void* parameter ) 113 | { 114 | #if defined(Master) 115 | //Serial.print( "hostWebServer runs on core: " ); 116 | //Serial.println( xPortGetCoreID() ); 117 | 118 | // Initialize LED 119 | pinMode( LEDPin, OUTPUT ); 120 | digitalWrite( LEDPin, LOW ); 121 | 122 | // Check for existence of the html file 123 | if ( !SD.exists( "/server/layout.htm" ) ) 124 | { 125 | Serial.println( "ERROR - Can't find layout.htm file!" ); 126 | for(;;); 127 | } 128 | 129 | // Setup the webserwer and the websocket on it. 130 | AsyncWebServer webserver( HTTP_PORT ); 131 | AsyncWebSocket ws( "/ws" ); 132 | 133 | addWebSocket( ws, webserver, handleWebSocketEvent ); 134 | addMDNS( mDNSname ); 135 | 136 | initWebServer( webserver ); 137 | 138 | combinedWiFiDeviceData combinedData; 139 | 140 | for(;;) 141 | { 142 | cleanUpWebSocketClients( ws ); 143 | digitalWrite( LEDPin, ledState ); 144 | 145 | BaseType_t wasSomethingReceived = xQueueReceive( g_queueBetweenHandleAndWebServer, &combinedData, 1 ); 146 | 147 | // If the queue was not empty and stream was requested, we should log and stream. 148 | if ( wasSomethingReceived && startToStreamData ) 149 | { 150 | logToCsvFileOnSD( combinedData, "/server/data/logfile.csv" ); 151 | sendCombinedDeviceDataToClient( ws, combinedData ); 152 | printCombinedWiFiDeviceData( combinedData ); 153 | OLEDdisplayForWebServer(); 154 | loggedDataCounter++; 155 | } 156 | // If the queue was not empty but no stream request was done, we should only log the data. 157 | else if ( wasSomethingReceived ) 158 | { 159 | logToCsvFileOnSD( combinedData, "/server/data/logfile.csv" ); 160 | printCombinedWiFiDeviceData( combinedData ); 161 | loggedDataCounter++; 162 | } 163 | 164 | // Let idle task run shortly. It is responsible of memory clean-up after a task was deleted. 165 | // See: https://www.freertos.org/RTOS-idle-task.html 166 | // Currently vTaskDelete() is not called in the code, 167 | // but that might be changed some time causing issues, if idle is starved of memory. 168 | vTaskDelay( 1 / portTICK_PERIOD_MS ); 169 | } 170 | #endif 171 | 172 | } // hostWebServer 173 | -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/src/interactData.cpp: -------------------------------------------------------------------------------- 1 | /* Header file belonging to this implementation */ 2 | #include "interactData.hpp" 3 | 4 | /* External header files */ 5 | #include 6 | #include 7 | 8 | /* Project header files */ 9 | #include "interactNTP.hpp" 10 | 11 | 12 | 13 | // print RSSI received from each slave in the first round to check whether they are doing their job well... 14 | bool printSlave1State = true; 15 | bool printSlave2State = true; 16 | bool printSlave3State = true; 17 | bool printSlave4State = true; 18 | 19 | 20 | 21 | void demoModeDataProcessor( const wifiDevicePayload payload, int8_t* rawRSSI ) 22 | { 23 | char strPhotoDeviceMAC[18] = {0}; 24 | MACnumberTostring( strPhotoDeviceMAC, g_espConfigData.photo_device_MAC ); 25 | 26 | if ( strcmp( payload.mac, strPhotoDeviceMAC ) == 0 ) // check if the received MAC address equals to the photo device's MAC 27 | { 28 | //Serial.println( "MAC matched!" ); 29 | 30 | if (payload.slaveID == 1) 31 | { 32 | rawRSSI[0] = payload.rssi; 33 | 34 | if (printSlave1State) 35 | { 36 | Serial.print( F("raw RSSI 1 = ") ); 37 | Serial.println( rawRSSI[0] ); 38 | 39 | printSlave1State = false; 40 | } 41 | } 42 | else if (payload.slaveID == 2) 43 | { 44 | rawRSSI[1] = payload.rssi; 45 | 46 | if (printSlave2State) 47 | { 48 | Serial.print( F("raw RSSI 2 = ") ); 49 | Serial.println( rawRSSI[1] ); 50 | 51 | printSlave2State = false; 52 | } 53 | } 54 | else if (payload.slaveID == 3) 55 | { 56 | rawRSSI[2] = payload.rssi; 57 | 58 | if (printSlave3State) 59 | { 60 | Serial.print( F("raw RSSI 3 = ") ); 61 | Serial.println( rawRSSI[2] ); 62 | 63 | printSlave3State = false; 64 | } 65 | } 66 | else if (payload.slaveID == 4) 67 | { 68 | rawRSSI[3] = payload.rssi; 69 | 70 | if (printSlave4State) 71 | { 72 | Serial.print( F("raw RSSI 4 = ") ); 73 | Serial.println( rawRSSI[3] ); 74 | 75 | printSlave4State = false; 76 | } 77 | } 78 | 79 | if ( std::find( rawRSSI, rawRSSI + g_espConfigData.number_of_slaves, 0 ) == rawRSSI + g_espConfigData.number_of_slaves ) // check if there are as many RSSI values belonging to photo device as the number of slaves have been all collected 80 | { 81 | Serial.println( F("\nPreparing sniffed data for web server...\n") ); 82 | 83 | combinedWiFiDeviceData combinedData; 84 | 85 | char now[30]; 86 | getTextFormatTimeinMilliseconds( now ); 87 | strcpy( combinedData.timestamp, now ); 88 | 89 | std::copy( std::begin(g_espConfigData.photo_device_MAC.mac), std::end(g_espConfigData.photo_device_MAC.mac), std::begin(combinedData.mac.mac) ); 90 | 91 | for ( int i = 0; i < g_espConfigData.number_of_slaves; i++ ) 92 | { 93 | combinedData.rssis[i] = rawRSSI[i]; 94 | } 95 | 96 | //printCombinedWiFiDeviceData( combinedData ); 97 | 98 | xQueueSend( g_queueBetweenHandleAndWebServer, &combinedData, 0 ); 99 | 100 | std::fill( rawRSSI, rawRSSI + g_espConfigData.number_of_slaves, 0 ); // re-initialize the array rawRSSI 101 | } 102 | } 103 | 104 | } // demoModeDataProcessor 105 | 106 | 107 | void normalModeDataProcessor( const wifiDevicePayload payload, std::vector &buffer ) 108 | { 109 | MacAddr payloadMAC; 110 | stringToMACnumber( payload.mac, payloadMAC ); 111 | 112 | auto it = std::find_if( buffer.begin(), buffer.end(), [payloadMAC] ( const combinedWiFiDeviceData& existingDevice ) 113 | { 114 | return std::equal( std::begin(existingDevice.mac.mac), std::end(existingDevice.mac.mac), std::begin(payloadMAC.mac) ); 115 | } ); 116 | 117 | if ( it != buffer.end() ) // The incoming device already exists on buffer 118 | { 119 | int index = std::distance( buffer.begin(), it ); 120 | 121 | if (payload.slaveID == 1) 122 | { 123 | buffer[index].rssis[0] = payload.rssi; 124 | } 125 | else if (payload.slaveID == 2) 126 | { 127 | buffer[index].rssis[1] = payload.rssi; 128 | } 129 | else if (payload.slaveID == 3) 130 | { 131 | buffer[index].rssis[2] = payload.rssi; 132 | } 133 | else if (payload.slaveID == 4) 134 | { 135 | buffer[index].rssis[3] = payload.rssi; 136 | } 137 | 138 | auto indexOfZero = std::find( buffer[index].rssis, buffer[index].rssis + MAX_NUMBER_OF_SLAVES, 0 ); 139 | 140 | if ( indexOfZero == buffer[index].rssis + g_espConfigData.number_of_slaves ) // check if there are as many RSSI values belonging to payloadMAC as the number of slaves have been all collected 141 | { 142 | Serial.println( F("\nPreparing sniffed data for web server...\n") ); 143 | 144 | char now[30]; 145 | getTextFormatTimeinMilliseconds( now ); 146 | strcpy( buffer[index].timestamp, now ); 147 | 148 | //printCombinedWiFiDeviceData( buffer[index] ); 149 | 150 | xQueueSend( g_queueBetweenHandleAndWebServer, &buffer[index], 0 ); 151 | 152 | std::fill( buffer[index].rssis, buffer[index].rssis + MAX_NUMBER_OF_SLAVES, 0 ); // re-initialize the array buffer[index].rssis 153 | } 154 | } 155 | else // The incoming device doesn't exsit on buffer. Create a new combinedWiFiDeviceData object for it. 156 | { 157 | combinedWiFiDeviceData newDevice; 158 | 159 | std::copy( std::begin(payloadMAC.mac), std::end(payloadMAC.mac), std::begin(newDevice.mac.mac) ); 160 | 161 | if (payload.slaveID == 1) 162 | { 163 | newDevice.rssis[0] = payload.rssi; 164 | } 165 | else if (payload.slaveID == 2) 166 | { 167 | newDevice.rssis[1] = payload.rssi; 168 | } 169 | else if (payload.slaveID == 3) 170 | { 171 | newDevice.rssis[2] = payload.rssi; 172 | } 173 | else if (payload.slaveID == 4) 174 | { 175 | newDevice.rssis[3] = payload.rssi; 176 | } 177 | 178 | buffer.push_back(newDevice); 179 | } 180 | 181 | } // normalModeDataProcessor -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/data/server/tabs.js: -------------------------------------------------------------------------------- 1 | /* Functions for displaying information on the tabular part of the webpage. */ 2 | 3 | 4 | function openTab( tabName, elmnt, color ) { 5 | 6 | document.getElementById("mapPage").style.display = "none"; 7 | document.getElementById("posDataPage").style.display = "none"; 8 | document.getElementById("measDataPage").style.display = "none"; 9 | 10 | document.getElementById("mapTab").style.backgroundColor = ""; 11 | document.getElementById("posTableTab").style.backgroundColor = ""; 12 | document.getElementById("measTableTab").style.backgroundColor = ""; 13 | 14 | document.getElementById( tabName ).style.display = "block"; 15 | elmnt.style.backgroundColor = color; 16 | 17 | console.log( tabName ); 18 | 19 | if ( tabName == "mapPage" ) { 20 | drawSetup( masterCoords, espSlaveXcoords, espSlaveYcoords, roomContourXCoords, roomContourYCoords ); 21 | } 22 | 23 | } // openTab 24 | 25 | 26 | function appendDataToLogsTable( jsondata ) { 27 | 28 | var dataTableBody = document.getElementById("measDataTableBody"); 29 | 30 | var tr = document.createElement('tr'); 31 | tr.innerHTML = 32 | '' + jsondata.time + '' + 33 | '' + jsondata.MAC + ''; 34 | for ( var i = 0; i < jsondata.RSSIs.length; i++) 35 | { 36 | tr.innerHTML = tr.innerHTML + 37 | '' + jsondata.RSSIs[i] + ''; 38 | } 39 | 40 | dataTableBody.appendChild(tr); 41 | 42 | } // appendDataToLogsTable 43 | 44 | 45 | function appendPosToPosTable( jsondata ) { 46 | var pos = positionNLLS( espSlaveXcoords, espSlaveYcoords, jsondata.RSSIs, RSSIc, pathLossExp ); 47 | var x_pos = pos[0]; 48 | var y_pos = pos[1]; 49 | 50 | var posTableBody = document.getElementById("posDataTableBody"); 51 | 52 | var tr = document.createElement('tr'); 53 | tr.innerHTML = 54 | '' + jsondata.time + '' + 55 | '' + jsondata.MAC + '' + 56 | '' + x_pos.toFixed(4) + '' + 57 | '' + y_pos.toFixed(4) + ''; 58 | 59 | posTableBody.appendChild(tr); 60 | 61 | // Draw position to the canvas on the "Map" tab. 62 | drawPosition( x_pos, y_pos ); 63 | 64 | } // appendPosToPosTable 65 | 66 | 67 | function drawSetup( masterCoords, espSlaveXcoords, espSlaveYcoords, roomContourXCoords, roomContourYCoords ) { 68 | var canvas = document.getElementById("Map"); 69 | 70 | if (canvas.getContext) { 71 | var ctx = canvas.getContext("2d"); 72 | 73 | 74 | // Draw room contours 75 | ctx.lineWidth = 0.006 * ctx.canvas.width; 76 | ctx.lineHeight = 0.006 * ctx.canvas.height; 77 | ctx.beginPath(); 78 | var contourPoint_x = ( ctx.canvas.width - 2 * edgeGapSize * ctx.canvas.width ) * ( roomContourXCoords[0] / roomMaxLength ) + edgeGapSize * ctx.canvas.width; 79 | var contourPoint_y = ( ctx.canvas.height - 2 * edgeGapSize * ctx.canvas.height ) * ( roomContourYCoords[0] / roomMaxWidth ) + edgeGapSize * ctx.canvas.height; 80 | 81 | ctx.moveTo( contourPoint_x, contourPoint_y ); 82 | for ( var i = 1; i < roomContourXCoords.length; i++ ) 83 | { 84 | var contourPoint_x = ( ctx.canvas.width - 2 * edgeGapSize * ctx.canvas.width ) * ( roomContourXCoords[i] / roomMaxLength ) + edgeGapSize * ctx.canvas.width; 85 | var contourPoint_y = ( ctx.canvas.height - 2 * edgeGapSize * ctx.canvas.height ) * ( roomContourYCoords[i] / roomMaxWidth ) + edgeGapSize * ctx.canvas.height; 86 | 87 | ctx.lineTo( contourPoint_x, contourPoint_y ); 88 | } 89 | ctx.closePath(); 90 | ctx.strokeStyle = "black"; 91 | ctx.stroke(); 92 | 93 | 94 | // Plot master position 95 | var master_x = ( ctx.canvas.width - 2 * edgeGapSize * ctx.canvas.width ) * ( masterCoords[0] / roomMaxLength ) + edgeGapSize * ctx.canvas.width; 96 | var master_y = ( ctx.canvas.height - 2 * edgeGapSize * ctx.canvas.height ) * ( masterCoords[1] / roomMaxWidth ) + edgeGapSize * ctx.canvas.height; 97 | 98 | ctx.beginPath(); 99 | ctx.lineWidth = 8; 100 | ctx.strokeStyle = "green"; 101 | ctx.rect( master_x - 28, master_y - 28, 60, 60 ); 102 | ctx.stroke(); 103 | 104 | ctx.font = "bold 48px Arial"; 105 | ctx.fillStyle = "green"; 106 | ctx.fillText("M", master_x - 18, master_y + 17 ); 107 | 108 | if ( demo ) { 109 | 110 | console.log( demo ); 111 | 112 | var dist_vertex_to_midpoint = 30; 113 | 114 | var demo_device_x = ( ctx.canvas.width - 2 * edgeGapSize * ctx.canvas.width ) * ( demo_device_position[0] / roomMaxLength ) + edgeGapSize * ctx.canvas.width; 115 | var demo_device_y = ( ctx.canvas.height - 2 * edgeGapSize * ctx.canvas.height ) * ( demo_device_position[1] / roomMaxWidth ) + edgeGapSize * ctx.canvas.height; 116 | 117 | console.log( demo_device_x ); 118 | console.log( demo_device_y ); 119 | 120 | ctx.beginPath(); 121 | ctx.lineWidth = 8; 122 | ctx.moveTo( demo_device_x, demo_device_y - 30 ); 123 | ctx.lineTo( demo_device_x - 30 * Math.cos(30 * Math.PI / 180), demo_device_y + 30 * Math.sin(30 * Math.PI / 180) ); 124 | ctx.lineTo( demo_device_x + 30 * Math.cos(30 * Math.PI / 180), demo_device_y + 30 * Math.sin(30 * Math.PI / 180) ); 125 | ctx.closePath(); 126 | ctx.closePath(); 127 | ctx.strokeStyle = "black"; 128 | ctx.stroke(); 129 | 130 | ctx.fillStyle = "yellow"; 131 | ctx.fill(); 132 | 133 | } 134 | 135 | 136 | // Plot slave positions 137 | for ( var i = 0; i < espSlaveXcoords.length; i++ ) 138 | { 139 | var slave_x = ( ctx.canvas.width - 2 * edgeGapSize * ctx.canvas.width ) * ( espSlaveXcoords[i] / roomMaxLength ) + edgeGapSize * ctx.canvas.width; 140 | var slave_y = ( ctx.canvas.height - 2 * edgeGapSize * ctx.canvas.height ) * ( espSlaveYcoords[i] / roomMaxWidth ) + edgeGapSize * ctx.canvas.height; 141 | 142 | console.log( slave_x ); 143 | console.log( slave_y ); 144 | 145 | ctx.beginPath(); 146 | ctx.lineWidth = 8; 147 | ctx.strokeStyle = "blue"; 148 | ctx.rect( slave_x - 28, slave_y - 35, 60, 60 ); 149 | ctx.stroke(); 150 | 151 | ctx.font = "bold 48px Arial"; 152 | ctx.fillStyle = "blue"; 153 | ctx.fillText( String(i+1), slave_x - 12, slave_y + 14 ); 154 | } 155 | } 156 | else { 157 | alert("Oh boy.. your browser is dumb.."); 158 | } 159 | 160 | } // drawRoom 161 | 162 | 163 | function drawPosition( x, y ) { 164 | 165 | var ctx = document.getElementById("Map").getContext("2d"); 166 | var pointSize = 0.005 * ctx.canvas.width; 167 | 168 | var x_pos = ( ctx.canvas.width - 2 * edgeGapSize * ctx.canvas.width ) * ( x / roomMaxLength ) + 169 | edgeGapSize * ctx.canvas.width; 170 | var y_pos = ( ctx.canvas.height - 2 * edgeGapSize * ctx.canvas.height ) * ( y / roomMaxWidth ) + 171 | edgeGapSize * ctx.canvas.height; 172 | 173 | ctx.fillStyle = "#ff2626"; 174 | ctx.beginPath(); //Start path 175 | ctx.arc( x_pos, y_pos, pointSize, 0, Math.PI * 2, true); // Draw a point using the arc function of the canvas with a point structure. 176 | ctx.fill(); // Close the path and fill. 177 | 178 | } // drawPosition -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/sdcardcontent/server/tabs.js: -------------------------------------------------------------------------------- 1 | /* Functions for displaying information on the tabular part of the webpage. */ 2 | 3 | 4 | function openTab( tabName, elmnt, color ) { 5 | 6 | document.getElementById("mapPage").style.display = "none"; 7 | document.getElementById("posDataPage").style.display = "none"; 8 | document.getElementById("measDataPage").style.display = "none"; 9 | 10 | document.getElementById("mapTab").style.backgroundColor = ""; 11 | document.getElementById("posTableTab").style.backgroundColor = ""; 12 | document.getElementById("measTableTab").style.backgroundColor = ""; 13 | 14 | document.getElementById( tabName ).style.display = "block"; 15 | elmnt.style.backgroundColor = color; 16 | 17 | console.log( tabName ); 18 | 19 | if ( tabName == "mapPage" ) { 20 | drawSetup( masterCoords, espSlaveXcoords, espSlaveYcoords, roomContourXCoords, roomContourYCoords ); 21 | } 22 | 23 | } // openTab 24 | 25 | 26 | function appendDataToLogsTable( jsondata ) { 27 | 28 | var dataTableBody = document.getElementById("measDataTableBody"); 29 | 30 | var tr = document.createElement('tr'); 31 | tr.innerHTML = 32 | '' + jsondata.time + '' + 33 | '' + jsondata.MAC + ''; 34 | for ( var i = 0; i < jsondata.RSSIs.length; i++) 35 | { 36 | tr.innerHTML = tr.innerHTML + 37 | '' + jsondata.RSSIs[i] + ''; 38 | } 39 | 40 | dataTableBody.appendChild(tr); 41 | 42 | } // appendDataToLogsTable 43 | 44 | 45 | function appendPosToPosTable( jsondata ) { 46 | var pos = positionNLLS( espSlaveXcoords, espSlaveYcoords, jsondata.RSSIs, RSSIc, pathLossExp ); 47 | var x_pos = pos[0]; 48 | var y_pos = pos[1]; 49 | 50 | var posTableBody = document.getElementById("posDataTableBody"); 51 | 52 | var tr = document.createElement('tr'); 53 | tr.innerHTML = 54 | '' + jsondata.time + '' + 55 | '' + jsondata.MAC + '' + 56 | '' + x_pos.toFixed(4) + '' + 57 | '' + y_pos.toFixed(4) + ''; 58 | 59 | posTableBody.appendChild(tr); 60 | 61 | // Draw position to the canvas on the "Map" tab. 62 | drawPosition( x_pos, y_pos ); 63 | 64 | } // appendPosToPosTable 65 | 66 | 67 | function drawSetup( masterCoords, espSlaveXcoords, espSlaveYcoords, roomContourXCoords, roomContourYCoords ) { 68 | var canvas = document.getElementById("Map"); 69 | 70 | if (canvas.getContext) { 71 | var ctx = canvas.getContext("2d"); 72 | 73 | 74 | // Draw room contours 75 | ctx.lineWidth = 0.006 * ctx.canvas.width; 76 | ctx.lineHeight = 0.006 * ctx.canvas.height; 77 | ctx.beginPath(); 78 | var contourPoint_x = ( ctx.canvas.width - 2 * edgeGapSize * ctx.canvas.width ) * ( roomContourXCoords[0] / roomMaxLength ) + edgeGapSize * ctx.canvas.width; 79 | var contourPoint_y = ( ctx.canvas.height - 2 * edgeGapSize * ctx.canvas.height ) * ( roomContourYCoords[0] / roomMaxWidth ) + edgeGapSize * ctx.canvas.height; 80 | 81 | ctx.moveTo( contourPoint_x, contourPoint_y ); 82 | for ( var i = 1; i < roomContourXCoords.length; i++ ) 83 | { 84 | var contourPoint_x = ( ctx.canvas.width - 2 * edgeGapSize * ctx.canvas.width ) * ( roomContourXCoords[i] / roomMaxLength ) + edgeGapSize * ctx.canvas.width; 85 | var contourPoint_y = ( ctx.canvas.height - 2 * edgeGapSize * ctx.canvas.height ) * ( roomContourYCoords[i] / roomMaxWidth ) + edgeGapSize * ctx.canvas.height; 86 | 87 | ctx.lineTo( contourPoint_x, contourPoint_y ); 88 | } 89 | ctx.closePath(); 90 | ctx.strokeStyle = "black"; 91 | ctx.stroke(); 92 | 93 | 94 | // Plot master position 95 | var master_x = ( ctx.canvas.width - 2 * edgeGapSize * ctx.canvas.width ) * ( masterCoords[0] / roomMaxLength ) + edgeGapSize * ctx.canvas.width; 96 | var master_y = ( ctx.canvas.height - 2 * edgeGapSize * ctx.canvas.height ) * ( masterCoords[1] / roomMaxWidth ) + edgeGapSize * ctx.canvas.height; 97 | 98 | ctx.beginPath(); 99 | ctx.lineWidth = 8; 100 | ctx.strokeStyle = "green"; 101 | ctx.rect( master_x - 28, master_y - 28, 60, 60 ); 102 | ctx.stroke(); 103 | 104 | ctx.font = "bold 48px Arial"; 105 | ctx.fillStyle = "green"; 106 | ctx.fillText("M", master_x - 18, master_y + 17 ); 107 | 108 | if ( demo ) { 109 | 110 | console.log( demo ); 111 | 112 | var dist_vertex_to_midpoint = 30; 113 | 114 | var demo_device_x = ( ctx.canvas.width - 2 * edgeGapSize * ctx.canvas.width ) * ( demo_device_position[0] / roomMaxLength ) + edgeGapSize * ctx.canvas.width; 115 | var demo_device_y = ( ctx.canvas.height - 2 * edgeGapSize * ctx.canvas.height ) * ( demo_device_position[1] / roomMaxWidth ) + edgeGapSize * ctx.canvas.height; 116 | 117 | console.log( demo_device_x ); 118 | console.log( demo_device_y ); 119 | 120 | ctx.beginPath(); 121 | ctx.lineWidth = 8; 122 | ctx.moveTo( demo_device_x, demo_device_y - 30 ); 123 | ctx.lineTo( demo_device_x - 30 * Math.cos(30 * Math.PI / 180), demo_device_y + 30 * Math.sin(30 * Math.PI / 180) ); 124 | ctx.lineTo( demo_device_x + 30 * Math.cos(30 * Math.PI / 180), demo_device_y + 30 * Math.sin(30 * Math.PI / 180) ); 125 | ctx.closePath(); 126 | ctx.closePath(); 127 | ctx.strokeStyle = "black"; 128 | ctx.stroke(); 129 | 130 | ctx.fillStyle = "yellow"; 131 | ctx.fill(); 132 | 133 | } 134 | 135 | 136 | // Plot slave positions 137 | for ( var i = 0; i < espSlaveXcoords.length; i++ ) 138 | { 139 | var slave_x = ( ctx.canvas.width - 2 * edgeGapSize * ctx.canvas.width ) * ( espSlaveXcoords[i] / roomMaxLength ) + edgeGapSize * ctx.canvas.width; 140 | var slave_y = ( ctx.canvas.height - 2 * edgeGapSize * ctx.canvas.height ) * ( espSlaveYcoords[i] / roomMaxWidth ) + edgeGapSize * ctx.canvas.height; 141 | 142 | console.log( slave_x ); 143 | console.log( slave_y ); 144 | 145 | ctx.beginPath(); 146 | ctx.lineWidth = 8; 147 | ctx.strokeStyle = "blue"; 148 | ctx.rect( slave_x - 28, slave_y - 35, 60, 60 ); 149 | ctx.stroke(); 150 | 151 | ctx.font = "bold 48px Arial"; 152 | ctx.fillStyle = "blue"; 153 | ctx.fillText( String(i+1), slave_x - 12, slave_y + 14 ); 154 | } 155 | } 156 | else { 157 | alert("Oh boy.. your browser is dumb.."); 158 | } 159 | 160 | } // drawRoom 161 | 162 | 163 | function drawPosition( x, y ) { 164 | 165 | var ctx = document.getElementById("Map").getContext("2d"); 166 | var pointSize = 0.005 * ctx.canvas.width; 167 | 168 | var x_pos = ( ctx.canvas.width - 2 * edgeGapSize * ctx.canvas.width ) * ( x / roomMaxLength ) + 169 | edgeGapSize * ctx.canvas.width; 170 | var y_pos = ( ctx.canvas.height - 2 * edgeGapSize * ctx.canvas.height ) * ( y / roomMaxWidth ) + 171 | edgeGapSize * ctx.canvas.height; 172 | 173 | ctx.fillStyle = "#ff2626"; 174 | ctx.beginPath(); //Start path 175 | ctx.arc( x_pos, y_pos, pointSize, 0, Math.PI * 2, true); // Draw a point using the arc function of the canvas with a point structure. 176 | ctx.fill(); // Close the path and fill. 177 | 178 | } // drawPosition -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/data/server/posDet.js: -------------------------------------------------------------------------------- 1 | /* Functions for determining position based on RSSI values. */ 2 | 3 | 4 | /* 5 | Returns the square of the distance between esp and device 6 | estimated by the log-normal shadowing model (lnsm). 7 | */ 8 | function lnsmDistanceEstimateSquared( rssi, one_meter_rssi, path_loss_exponent ) { 9 | return Math.pow( 10, ( 2 * (one_meter_rssi - rssi) / (10 * path_loss_exponent) ) ); 10 | 11 | } // lnsmDistanceEstimateSquared 12 | 13 | 14 | /* 15 | Returns the eucledian distnace squared of the device and the esp. 16 | */ 17 | function distanceSquared( x_0, y_0, x_esp, y_esp ) { 18 | return Math.pow( (x_0 - x_esp), 2 ) + Math.pow( (y_0 - y_esp), 2 ); 19 | 20 | } // distanceSquared 21 | 22 | 23 | /* 24 | Returns the eucledian norm of a vector. 25 | */ 26 | function normOfVector( vector ) { 27 | var norm = 0; 28 | 29 | for ( var i = 0; i < vector.length; i++ ) 30 | { 31 | norm += ( vector[i] * vector[i] ); 32 | } 33 | 34 | return Math.sqrt( norm ); 35 | 36 | } // normOfVector 37 | 38 | 39 | /* 40 | Returns the difference of squares of the lnsm-estimated distance from esps 41 | and the euclidien distance of the current calculated position from esps . 42 | */ 43 | function calculateErrorVector( x_0, y_0, rssis, x_esps, y_esps, one_meter_rssi, path_loss_exponent ) { 44 | var errorVect = []; 45 | for ( var i = 0; i < x_esps.length; i++ ) 46 | { 47 | errorVect[i] = lnsmDistanceEstimateSquared( rssis[i], one_meter_rssi, path_loss_exponent) 48 | - distanceSquared( x_0, y_0, x_esps[i], y_esps[i] ); 49 | } 50 | 51 | return errorVect; 52 | 53 | } // calculateErrorVector 54 | 55 | 56 | /* 57 | Calculates the Jacobian of the eucledian distance function 58 | evaluated for the given device and esp positions. 59 | */ 60 | function jacobian( x_0, y_0, x_esps, y_esps) { 61 | const m = x_esps.length; 62 | const n = 2; 63 | 64 | var J = Array.apply(null, Array(m)); // create an empty array of length n 65 | for (var i = 0; i < m; i++) { 66 | J[i] = Array.apply(null, Array(n)); // make each element an array 67 | } 68 | 69 | for ( var i = 0; i < x_esps.length; i++ ) 70 | { 71 | J[i][0] = 2 * ( x_0 - x_esps[i] ); 72 | J[i][1] = 2 * ( y_0 - y_esps[i] ); 73 | } 74 | 75 | /* 76 | console.log("J calculated"); 77 | for ( var i=0; i < J.length; i++ ) 78 | { 79 | console.log( J[i] ); 80 | } 81 | console.log(J.length); 82 | */ 83 | 84 | return J; 85 | } 86 | 87 | 88 | function transpJtimesJ( J ) { 89 | 90 | tJJ = [[0, 0],[0, 0]]; 91 | 92 | for ( var i = 0; i < J.length; i++ ) 93 | { 94 | tJJ[0][0] += J[i][0] * J[i][0]; 95 | tJJ[0][1] += J[i][0] * J[i][1]; 96 | tJJ[1][0] += J[i][1] * J[i][0]; 97 | tJJ[1][1] += J[i][1] * J[i][1]; 98 | } 99 | 100 | return tJJ; 101 | 102 | } // transpJtimesJ 103 | 104 | 105 | function transpJtimesVect( J, v ) { 106 | 107 | var tJv = [0, 0]; 108 | 109 | for ( var i = 0; i < v.length; i++ ) 110 | { 111 | tJv[0] += J[i][0] * v[i]; 112 | tJv[1] += J[i][1] * v[i]; 113 | } 114 | 115 | return tJv; 116 | 117 | } // transpJtimesVect 118 | 119 | 120 | /* 121 | The initial position guess for the non-linear least squares iteration is taken 122 | as the position of the closest esp device based on the lnsm-estimated distance. 123 | */ 124 | function initPosGuess( x_esps, y_esps, rssis, one_meter_rssi, path_loss_exponent ) { 125 | var lnsmDistSquares = []; 126 | 127 | for ( var i = 0; i < x_esps.length; i++ ) 128 | { 129 | lnsmDistSquares[i] = lnsmDistanceEstimateSquared( rssis[i], one_meter_rssi, path_loss_exponent ); 130 | } 131 | 132 | var indexOfMin = lnsmDistSquares.indexOf( Math.min(...lnsmDistSquares) ); 133 | 134 | var iPG = [ x_esps [indexOfMin], y_esps[indexOfMin] ]; 135 | 136 | return iPG; 137 | 138 | } // initPosGuess 139 | 140 | 141 | /* 142 | Gauss solver: direct equation system solver. Since ours is only a 2x2 system 143 | (we are looking for 2 position values, which minimise the error in least squares sense) 144 | the bad complexity of the solver shouldn't be a huge issue. 145 | Still, it could be changed for something more sophisticated... 146 | */ 147 | function gaussSolver( A, b ) { 148 | var len = b.length; 149 | var deltaPos = [0, 0]; 150 | var toleranceForZero = 0.000001; 151 | 152 | var absOfDetA = Math.abs( A[0][0] * A[1][1] - A[0][1]*A[1][0] ); 153 | 154 | if ( absOfDetA < 0.01 ) { 155 | console.log( "Determinant is zero" ); 156 | } 157 | 158 | for ( var k = 0; k < (len-1); k++ ) 159 | { 160 | for ( var i = (k+1); i < len; i++ ) 161 | { 162 | if ( A[k][k] > toleranceForZero ) 163 | { 164 | A[i][k] = A[i][k] / A[k][k]; 165 | } 166 | else 167 | { 168 | console.log("Pivot is zero!"); 169 | } 170 | for ( var j = (k+1); j < len; j++) 171 | { 172 | A[i][j] = A[i][j] - A[i][k] * A[k][j]; 173 | } 174 | } 175 | } 176 | 177 | for ( var k = 1; k < len; k++) 178 | { 179 | for ( var i = 0; i < (k-1); i++ ) 180 | { 181 | b[k] = b[k] - A[k][i] * b[i]; 182 | } 183 | } 184 | 185 | for ( var k = (len-1); k >= 0; k-- ) 186 | { 187 | for ( var i = (k+1); i < len; i++ ) 188 | { 189 | b[k] = b[k] - A[k][i] * deltaPos[i]; 190 | } 191 | deltaPos[k] = b[k] / A[k][k]; 192 | } 193 | 194 | return deltaPos; 195 | 196 | } // gaussSolver 197 | 198 | 199 | function vectorAdd( a, b ) { 200 | 201 | if ( a.length == b.length ) 202 | { 203 | var sum = []; 204 | for ( var i = 0; i < a.length; i++ ) 205 | { 206 | sum[i] = a[i] + b[i]; 207 | } 208 | } 209 | 210 | return sum; 211 | 212 | } // vectorAdd 213 | 214 | /* 215 | Non-linear leas squares iteration for determining the unknown position (c_0) of a device 216 | based on RSSi values measured by the esps. 217 | It solves the equation: transp(J)*J*deltaC_0 = transp(J)*R for each itaration. 218 | */ 219 | function positionNLLS( x_esps, y_esps, rssis, one_meter_rssi, path_loss_exponent ) { 220 | 221 | var errorTolerance = 0.01; 222 | var c_0 = initPosGuess( x_esps, y_esps, rssis, one_meter_rssi, path_loss_exponent ); 223 | console.log(c_0); 224 | var R = calculateErrorVector( c_0[0], c_0[1], rssis, x_esps, y_esps, one_meter_rssi, path_loss_exponent ); 225 | 226 | var X = [[0, 0],[0, 0]]; 227 | var b = [0, 0]; 228 | var deltaC_0 = [0, 0]; 229 | 230 | console.log( "New position iteration has started: "); 231 | 232 | for ( var i = 0; i < 10; i++ ) 233 | { 234 | J = jacobian( c_0[0], c_0[1], x_esps, y_esps, rssis, one_meter_rssi, path_loss_exponent ); 235 | //console.log( J ); 236 | X = transpJtimesJ( J ); 237 | //console.log( X ); 238 | b = transpJtimesVect( J, R ); 239 | //console.log( b ); 240 | deltaC_0 = gaussSolver( X, b ); 241 | console.log(deltaC_0); 242 | c_0 = vectorAdd( c_0, deltaC_0); 243 | console.log(c_0); 244 | R = calculateErrorVector( c_0[0], c_0[1], rssis, x_esps, y_esps, one_meter_rssi, path_loss_exponent ); 245 | console.log(R); 246 | } 247 | 248 | return c_0; 249 | 250 | } // positionNLLS -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/sdcardcontent/server/posDet.js: -------------------------------------------------------------------------------- 1 | /* Functions for determining position based on RSSI values. */ 2 | 3 | 4 | /* 5 | Returns the square of the distance between esp and device 6 | estimated by the log-normal shadowing model (lnsm). 7 | */ 8 | function lnsmDistanceEstimateSquared( rssi, one_meter_rssi, path_loss_exponent ) { 9 | return Math.pow( 10, ( 2 * (one_meter_rssi - rssi) / (10 * path_loss_exponent) ) ); 10 | 11 | } // lnsmDistanceEstimateSquared 12 | 13 | 14 | /* 15 | Returns the eucledian distnace squared of the device and the esp. 16 | */ 17 | function distanceSquared( x_0, y_0, x_esp, y_esp ) { 18 | return Math.pow( (x_0 - x_esp), 2 ) + Math.pow( (y_0 - y_esp), 2 ); 19 | 20 | } // distanceSquared 21 | 22 | 23 | /* 24 | Returns the eucledian norm of a vector. 25 | */ 26 | function normOfVector( vector ) { 27 | var norm = 0; 28 | 29 | for ( var i = 0; i < vector.length; i++ ) 30 | { 31 | norm += ( vector[i] * vector[i] ); 32 | } 33 | 34 | return Math.sqrt( norm ); 35 | 36 | } // normOfVector 37 | 38 | 39 | /* 40 | Returns the difference of squares of the lnsm-estimated distance from esps 41 | and the euclidien distance of the current calculated position from esps . 42 | */ 43 | function calculateErrorVector( x_0, y_0, rssis, x_esps, y_esps, one_meter_rssi, path_loss_exponent ) { 44 | var errorVect = []; 45 | for ( var i = 0; i < x_esps.length; i++ ) 46 | { 47 | errorVect[i] = lnsmDistanceEstimateSquared( rssis[i], one_meter_rssi, path_loss_exponent) 48 | - distanceSquared( x_0, y_0, x_esps[i], y_esps[i] ); 49 | } 50 | 51 | return errorVect; 52 | 53 | } // calculateErrorVector 54 | 55 | 56 | /* 57 | Calculates the Jacobian of the eucledian distance function 58 | evaluated for the given device and esp positions. 59 | */ 60 | function jacobian( x_0, y_0, x_esps, y_esps) { 61 | const m = x_esps.length; 62 | const n = 2; 63 | 64 | var J = Array.apply(null, Array(m)); // create an empty array of length n 65 | for (var i = 0; i < m; i++) { 66 | J[i] = Array.apply(null, Array(n)); // make each element an array 67 | } 68 | 69 | for ( var i = 0; i < x_esps.length; i++ ) 70 | { 71 | J[i][0] = 2 * ( x_0 - x_esps[i] ); 72 | J[i][1] = 2 * ( y_0 - y_esps[i] ); 73 | } 74 | 75 | /* 76 | console.log("J calculated"); 77 | for ( var i=0; i < J.length; i++ ) 78 | { 79 | console.log( J[i] ); 80 | } 81 | console.log(J.length); 82 | */ 83 | 84 | return J; 85 | } 86 | 87 | 88 | function transpJtimesJ( J ) { 89 | 90 | tJJ = [[0, 0],[0, 0]]; 91 | 92 | for ( var i = 0; i < J.length; i++ ) 93 | { 94 | tJJ[0][0] += J[i][0] * J[i][0]; 95 | tJJ[0][1] += J[i][0] * J[i][1]; 96 | tJJ[1][0] += J[i][1] * J[i][0]; 97 | tJJ[1][1] += J[i][1] * J[i][1]; 98 | } 99 | 100 | return tJJ; 101 | 102 | } // transpJtimesJ 103 | 104 | 105 | function transpJtimesVect( J, v ) { 106 | 107 | var tJv = [0, 0]; 108 | 109 | for ( var i = 0; i < v.length; i++ ) 110 | { 111 | tJv[0] += J[i][0] * v[i]; 112 | tJv[1] += J[i][1] * v[i]; 113 | } 114 | 115 | return tJv; 116 | 117 | } // transpJtimesVect 118 | 119 | 120 | /* 121 | The initial position guess for the non-linear least squares iteration is taken 122 | as the position of the closest esp device based on the lnsm-estimated distance. 123 | */ 124 | function initPosGuess( x_esps, y_esps, rssis, one_meter_rssi, path_loss_exponent ) { 125 | var lnsmDistSquares = []; 126 | 127 | for ( var i = 0; i < x_esps.length; i++ ) 128 | { 129 | lnsmDistSquares[i] = lnsmDistanceEstimateSquared( rssis[i], one_meter_rssi, path_loss_exponent ); 130 | } 131 | 132 | var indexOfMin = lnsmDistSquares.indexOf( Math.min(...lnsmDistSquares) ); 133 | 134 | var iPG = [ x_esps [indexOfMin], y_esps[indexOfMin] ]; 135 | 136 | return iPG; 137 | 138 | } // initPosGuess 139 | 140 | 141 | /* 142 | Gauss solver: direct equation system solver. Since ours is only a 2x2 system 143 | (we are looking for 2 position values, which minimise the error in least squares sense) 144 | the bad complexity of the solver shouldn't be a huge issue. 145 | Still, it could be changed for something more sophisticated... 146 | */ 147 | function gaussSolver( A, b ) { 148 | var len = b.length; 149 | var deltaPos = [0, 0]; 150 | var toleranceForZero = 0.000001; 151 | 152 | var absOfDetA = Math.abs( A[0][0] * A[1][1] - A[0][1]*A[1][0] ); 153 | 154 | if ( absOfDetA < 0.01 ) { 155 | console.log( "Determinant is zero" ); 156 | } 157 | 158 | for ( var k = 0; k < (len-1); k++ ) 159 | { 160 | for ( var i = (k+1); i < len; i++ ) 161 | { 162 | if ( A[k][k] > toleranceForZero ) 163 | { 164 | A[i][k] = A[i][k] / A[k][k]; 165 | } 166 | else 167 | { 168 | console.log("Pivot is zero!"); 169 | } 170 | for ( var j = (k+1); j < len; j++) 171 | { 172 | A[i][j] = A[i][j] - A[i][k] * A[k][j]; 173 | } 174 | } 175 | } 176 | 177 | for ( var k = 1; k < len; k++) 178 | { 179 | for ( var i = 0; i < (k-1); i++ ) 180 | { 181 | b[k] = b[k] - A[k][i] * b[i]; 182 | } 183 | } 184 | 185 | for ( var k = (len-1); k >= 0; k-- ) 186 | { 187 | for ( var i = (k+1); i < len; i++ ) 188 | { 189 | b[k] = b[k] - A[k][i] * deltaPos[i]; 190 | } 191 | deltaPos[k] = b[k] / A[k][k]; 192 | } 193 | 194 | return deltaPos; 195 | 196 | } // gaussSolver 197 | 198 | 199 | function vectorAdd( a, b ) { 200 | 201 | if ( a.length == b.length ) 202 | { 203 | var sum = []; 204 | for ( var i = 0; i < a.length; i++ ) 205 | { 206 | sum[i] = a[i] + b[i]; 207 | } 208 | } 209 | 210 | return sum; 211 | 212 | } // vectorAdd 213 | 214 | /* 215 | Non-linear leas squares iteration for determining the unknown position (c_0) of a device 216 | based on RSSi values measured by the esps. 217 | It solves the equation: transp(J)*J*deltaC_0 = transp(J)*R for each itaration. 218 | */ 219 | function positionNLLS( x_esps, y_esps, rssis, one_meter_rssi, path_loss_exponent ) { 220 | 221 | var errorTolerance = 0.01; 222 | var c_0 = initPosGuess( x_esps, y_esps, rssis, one_meter_rssi, path_loss_exponent ); 223 | console.log(c_0); 224 | var R = calculateErrorVector( c_0[0], c_0[1], rssis, x_esps, y_esps, one_meter_rssi, path_loss_exponent ); 225 | 226 | var X = [[0, 0],[0, 0]]; 227 | var b = [0, 0]; 228 | var deltaC_0 = [0, 0]; 229 | 230 | console.log( "New position iteration has started: "); 231 | 232 | for ( var i = 0; i < 10; i++ ) 233 | { 234 | J = jacobian( c_0[0], c_0[1], x_esps, y_esps, rssis, one_meter_rssi, path_loss_exponent ); 235 | //console.log( J ); 236 | X = transpJtimesJ( J ); 237 | //console.log( X ); 238 | b = transpJtimesVect( J, R ); 239 | //console.log( b ); 240 | deltaC_0 = gaussSolver( X, b ); 241 | console.log(deltaC_0); 242 | c_0 = vectorAdd( c_0, deltaC_0); 243 | console.log(c_0); 244 | R = calculateErrorVector( c_0[0], c_0[1], rssis, x_esps, y_esps, one_meter_rssi, path_loss_exponent ); 245 | console.log(R); 246 | } 247 | 248 | return c_0; 249 | 250 | } // positionNLLS -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/src/generalFunctions.cpp: -------------------------------------------------------------------------------- 1 | /* Header file belonging to this implementation */ 2 | #include "generalFunctions.hpp" 3 | 4 | /* External header files */ 5 | #include 6 | 7 | /* Project header files */ 8 | #include "globalVariables.hpp" 9 | #include "dataTypes.hpp" 10 | #include "interactNTP.hpp" 11 | #include "interactOLED.hpp" 12 | #include "interactNTP.hpp" 13 | 14 | 15 | // Declared global variables ( from "globalVariables.hpp" ) are defined here. 16 | QueueHandle_t g_queueBetweenMonitorAndSend = xQueueCreate( cg_queueSizeMonitorAndSend, sizeof( wifiDeviceData ) ); 17 | QueueHandle_t g_queueBetweenReceiveAndHandle = xQueueCreate( cg_queueSizeBetweenReceiveAndHandle, sizeof( wifiDevicePayload ) ); 18 | QueueHandle_t g_queueBetweenHandleAndWebServer = xQueueCreate( cg_queueSizeHandleAndWebServer, sizeof( combinedWiFiDeviceData ) ); 19 | 20 | configData g_espConfigData; 21 | 22 | 23 | 24 | // Functions which could not be categorized (yet) should be defined here. 25 | 26 | void stringToMACnumber( const char* charmac, MacAddr& mac ) 27 | { 28 | sscanf( charmac, "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX", 29 | &(mac.mac[0]), &(mac.mac[1]), &(mac.mac[2]), 30 | &(mac.mac[3]), &(mac.mac[4]), &(mac.mac[5]) ); 31 | 32 | } // stringToMACnumber 33 | 34 | 35 | void MACnumberTostring( char* stringMac, MacAddr& mac ) 36 | { 37 | // stringMac should point to an array of 18 characters. 38 | /* This char array must be able to accomodate a C-string. 39 | C-strings have a 0 at their ending, so \0 is added to every char array passed to the function. 40 | Our MAC format contains 2 * 6 = 12 characters for the 6 two digit hex values and 5 colons between them. 41 | Therefore we need: 12 (MACs) + 5 (colons) + 1 (0 at end) = 18 characters for buffer size. */ 42 | sprintf( stringMac, "%02X:%02X:%02X:%02X:%02X:%02X", 43 | mac.mac[0], mac.mac[1], mac.mac[2], 44 | mac.mac[3], mac.mac[4], mac.mac[5] ); // Formatting the data into the standard "MAC style". 45 | 46 | } // MACnumberTostring 47 | 48 | 49 | void timestampTostring( char* stringTimestamp, unsigned long timestamp ) 50 | { 51 | sprintf( stringTimestamp, "%lu", timestamp ); 52 | 53 | } // timestampTostring 54 | 55 | 56 | void packetCounterTostring( char* stringPacketCounter, unsigned int& packetCounter ) 57 | { 58 | sprintf( stringPacketCounter, "%u", packetCounter ); 59 | 60 | } // packetCounterTostring 61 | 62 | 63 | void RSSITostring( char* stringRSSI, int8_t& RSSI ) 64 | { 65 | sprintf( stringRSSI, "%d", RSSI ); 66 | 67 | } // RSSITostring 68 | 69 | 70 | std::string uint64_to_string( uint64_t value ) 71 | { 72 | std::ostringstream os; 73 | os << value; 74 | return os.str(); 75 | 76 | } // uint64_to_string 77 | 78 | 79 | std::string uint32_to_string( uint32_t value ) 80 | { 81 | std::ostringstream os; 82 | os << value; 83 | return os.str(); 84 | 85 | } // uint32_to_string 86 | 87 | 88 | void print_uint64_t(uint64_t num) 89 | { 90 | char rev[128]; 91 | char *p = rev + 1; 92 | 93 | while (num > 0) 94 | { 95 | *p++ = '0' + ( num % 10 ); 96 | num /= 10; 97 | } 98 | p--; 99 | 100 | // Print the number which is now in reverse 101 | while (p > rev) 102 | { 103 | Serial.print(*p--); 104 | } 105 | 106 | } // print_uint64_t 107 | 108 | 109 | JsonObject convertCombinedWifiDeviceDataToJSON( StaticJsonDocument<512>& staticJsonDoc, combinedWiFiDeviceData& deviceData ) 110 | { 111 | staticJsonDoc["time"] = deviceData.timestamp; 112 | 113 | char stringMac[18] = {0}; 114 | MACnumberTostring( stringMac, deviceData.mac ); 115 | staticJsonDoc["MAC"] = stringMac; 116 | 117 | JsonArray rssis = staticJsonDoc.createNestedArray("RSSIs"); 118 | for ( int i = 0; i < g_espConfigData.number_of_slaves; i++ ) 119 | { 120 | rssis.add( deviceData.rssis[i] ); 121 | } 122 | 123 | return staticJsonDoc.as(); 124 | 125 | } // convertCombinedWifiDeviceDataToJSON 126 | 127 | 128 | void sendCombinedDeviceDataToClient( AsyncWebSocket& wsserver, combinedWiFiDeviceData& combinedData ) 129 | { 130 | StaticJsonDocument<512> staticJsonDoc; 131 | JsonObject staticJsonObject; 132 | 133 | staticJsonObject = convertCombinedWifiDeviceDataToJSON( staticJsonDoc, combinedData ); 134 | 135 | // Safely larger than the number of characters the json will contain. 136 | char buffer[256]; 137 | serializeJson( staticJsonObject, buffer ); 138 | wsserver.text( streamRequestWithClientID, buffer ); 139 | 140 | } // sendCombinedDeviceDataToClient 141 | 142 | 143 | float distanceFromSniffingESP( float x_free, float y_free, float x_esp, float y_esp ) 144 | { 145 | return sqrt( (x_free - x_esp) * (x_free - x_esp) + (y_free - y_esp) * (y_free - y_esp) ); 146 | 147 | } // distanceFromSniffingESP 148 | 149 | 150 | combinedWiFiDeviceData generateRecordAtPointLNSMmodelFixedMac( float x_free, float y_free, int RSSIc, float path_loss_exp ) 151 | { 152 | combinedWiFiDeviceData combinedData; 153 | 154 | char now[30]; 155 | getTextFormatTimeinMilliseconds(now); 156 | strcpy( combinedData.timestamp, now ); 157 | 158 | // Data will be generated for a single arbitrary defined MAC address. 159 | combinedData.mac.mac[0] = 10; 160 | combinedData.mac.mac[1] = 10; 161 | combinedData.mac.mac[2] = 10; 162 | combinedData.mac.mac[3] = 10; 163 | combinedData.mac.mac[4] = 10; 164 | combinedData.mac.mac[5] = 10; 165 | 166 | for ( int i = 0; i < g_espConfigData.number_of_slaves; i++ ) 167 | { 168 | // The function uses the Log-normal shadowing radio propagation model: https://core.ac.uk/download/pdf/30312076.pdf 169 | // dc = 1m where RSSIc was measured. RSSI = RSSIc - 10 * path_loss_exp * log(dist[meter] / dc = 1 meter) 170 | combinedData.rssis[i] = (int8_t) round( RSSIc - 10 * path_loss_exp * log10( distanceFromSniffingESP( x_free, // X coord of tracked device. 171 | y_free, // Y coord of tracked device. 172 | g_espConfigData.slave_x_coords[i], 173 | g_espConfigData.slave_y_coords[i]) ) ); 174 | } 175 | 176 | return combinedData; 177 | 178 | } // generateRecordAtPointLNSMmodel 179 | 180 | 181 | // Function for generating random test recordings for the receiveAndLogWifiDeviceData() taskfunction when no slave devices are available. 182 | combinedWiFiDeviceData generateRandomRecordsFromSlaves() 183 | { 184 | combinedWiFiDeviceData combinedData; 185 | 186 | char now[30]; 187 | getTextFormatTimeinMilliseconds(now); 188 | strcpy( combinedData.timestamp, now ); 189 | 190 | MacAddr mac_add; 191 | for ( int j = 0; j < 6; j++ ) 192 | { 193 | mac_add.mac[j] = random( 0, 255 ); 194 | } 195 | combinedData.mac = mac_add; 196 | 197 | for ( int i = 0; i < g_espConfigData.number_of_slaves; i++ ) 198 | { 199 | combinedData.rssis[i] = -1 * random( 0, 100 ); 200 | } 201 | 202 | return combinedData; 203 | 204 | } // generateRecordsFromSlaves -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/src/main.cpp: -------------------------------------------------------------------------------- 1 | /* External header files */ 2 | #include 3 | 4 | /* Project header files */ 5 | #include "taskFunctionsForMaster.hpp" 6 | #include "taskFunctionsForSlave.hpp" 7 | #include "generalFunctions.hpp" 8 | #include "interactFileSystem.hpp" 9 | #include "interactSD.hpp" 10 | #include "interactSPIFFS.hpp" 11 | #include "interactLoRa.hpp" 12 | #include "interactOLED.hpp" 13 | 14 | 15 | 16 | void setup() 17 | { 18 | { 19 | // 1. Initialize serial monitor. 20 | Serial.begin( 115200 ); 21 | while( !Serial ) // wait for serial port to be available 22 | delay(500); 23 | Serial.println(); 24 | Serial.println( "Serial was started." ); 25 | 26 | // 2. Initialize OLED display. 27 | initializeOLED(); 28 | 29 | // Use SD card to configure ESP board & store captured packet data locally. 30 | // If the board doesn't have a SD card slot mounted, use SPI file system (SPIFFS) instead. 31 | // The relevant files must be put in the "data" folder (the same level as src folder) 32 | #if defined(Master) || defined(Slave_1) || defined(Slave_2) 33 | // 3. Initialize SD card. 34 | initializeSDCard(); 35 | #elif defined(Slave_3) || defined(Slave_4) 36 | // 3. Initialize SPI file system. 37 | initializeSIPFFS(); 38 | #endif 39 | 40 | // 4. Initialize LoRa radio. 41 | initializeLoRa(); 42 | } 43 | 44 | // Print the config file in the initial format. (just to compare whether info was read in properly.) 45 | printConfigFile( cg_path_to_config_JSON ); 46 | 47 | // Configure ESP board from JSON config file. 48 | configESPfromJSON( cg_path_to_config_JSON ); 49 | 50 | // Print the config data read in from JSON. 51 | printCurrentConfigData(); 52 | 53 | 54 | // Size of a "word" is CPU architecture dependent. Short explanation can be found here: 55 | // https://stackoverflow.com/questions/19821103/what-does-it-mean-by-word-size-in-computer 56 | // So the size of a word on ESP32 is 32 bit (4 bytes) since it has a 32-bit CPU. 57 | // Here only "safely large" stack sizes were allocated for the tasks, no exact calculation was done. 58 | 59 | 60 | if( !g_espConfigData.am_i_master ) // If the current device is a slave, the monitor and sender tasks will be created. 61 | { 62 | // Check whether the queue was created properly. 63 | if( g_queueBetweenMonitorAndSend == NULL ) 64 | { 65 | Serial.println( "An error occured when creating the queue between monitor and sender task." ); 66 | } 67 | 68 | // Task pinned to core 0. Monitors wifi traffic, captures data and puts it on the queue. 69 | xTaskCreatePinnedToCore( monitorWiFiDevices, // Task function 70 | "TaskForMonitoringWiFiDevices", // Task name 71 | 10000, // Stack size in words: So it is 10000 * 4 bytes here. Not precise just large enough. 72 | NULL, // Parameter passed as input to task 73 | 1, // Task priority 74 | NULL, // Task handle 75 | 0 ); // Core ID 76 | 77 | // Task pinned to core 1. Receives data from the queue. Logs to SD card and sends it to master. 78 | xTaskCreatePinnedToCore( sendAndLogWiFiDeviceData, // Task function 79 | "TaskForSendingWiFiDeviceData", // Task name 80 | 10000, // Stack size in words 81 | NULL, // Parameter passed as input to task 82 | 1, // Task priority 83 | NULL, // Task handle 84 | 1 ); // Core ID 85 | } 86 | else // If the current device is the master, the webserver and receiver tasks will be created. 87 | { 88 | // Check whether the queue was created properly. 89 | if( g_queueBetweenReceiveAndHandle == NULL ) 90 | { 91 | Serial.println( "An error occured when creating the queue between receiver and handler task." ); 92 | for(;;); // if failed, do nothing. 93 | } 94 | 95 | if( g_queueBetweenHandleAndWebServer == NULL ) 96 | { 97 | Serial.println( "An error occured when creating the queue between handler and webserver task." ); 98 | for(;;); // if failed, do nothing. 99 | } 100 | 101 | // OLED display 102 | OLEDdisplayForSlaveDevicesMac(); 103 | OLEDdisplayForLoRaReceiverInit(); 104 | 105 | // Task pinned to core 1. Hosts the webserver for data access. 106 | xTaskCreatePinnedToCore( hostWebServer, // Task function 107 | "TaskForHostingWebServer", // Task name 108 | 10000, // Stack size in words: So it is 10000 * 4 bytes here. Not precise, just large enough. 109 | NULL, // Parameter passed as input to task 110 | 1, // Task priority 111 | NULL, // Task handle 112 | 1 ); // Core ID: Server needs to run here, since watchdogs are disabled on core 1 (Arduino framework basis setup). 113 | // Therefore longer data exchange with server clients does not trigger them to reboot the system... 114 | // More info on watchdogs: https://www.embedded.com/introduction-to-watchdog-timers/ 115 | 116 | // Task pinned to core 0. Receives data from slaves. Provides it to the webserver. 117 | xTaskCreatePinnedToCore( receiveAndLogWiFiDeviceData, // Task function 118 | "TaskForSendingWiFiDeviceData", // Task name 119 | 10000, // Stack size in words 120 | NULL, // Parameter passed as input to task 121 | 1, // Task priority 122 | NULL, // Task handle 123 | 0 ); // Core ID 124 | } 125 | 126 | } // setup 127 | 128 | 129 | void loop() 130 | { 131 | // "loop" runs on core 1, if left empty it starves other tasks on this core of CPU time. 132 | // No proper fix for this was found, just this solution everywhere, where Free RTOS and esp-idf was combined with Arduino code. 133 | // Due to some issue with using Arduino framework over ESP boards: https://github.com/espressif/arduino-esp32/issues/595 134 | delay( 1 ); 135 | 136 | } // loop -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/src/interactOLED.cpp: -------------------------------------------------------------------------------- 1 | /* Header file belonging to this implementation */ 2 | #include "interactOLED.hpp" 3 | 4 | /* External heaader files */ 5 | //... 6 | 7 | /* Project header files */ 8 | #include "interactLoRa.hpp" 9 | #include "interactWiFi.hpp" 10 | #include "generalFunctions.hpp" 11 | #include "interactNTP.hpp" 12 | #include "interactWebServer.hpp" 13 | #include "globalVariables.hpp" 14 | 15 | 16 | Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, LEDPin, 100000UL, 100000UL); 17 | 18 | 19 | void initializeOLED() 20 | { 21 | Serial.println(); 22 | Serial.println( "Initializing OLED..." ); 23 | 24 | // Initialize LED 25 | pinMode(OLED_RST, OUTPUT); 26 | digitalWrite(OLED_RST, LOW); 27 | delay(20); 28 | digitalWrite(OLED_RST, HIGH); 29 | 30 | #if defined(Slave_4) 31 | Wire.begin(OLED_SDA, OLED_SCL); // This line ONLY FOR Heltec WiFi LoRa 32 V2!!! 32 | #else 33 | Wire.begin(); 34 | #endif 35 | 36 | // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally 37 | if( !display.begin( SSD1306_SWITCHCAPVCC, OLED_ADDRESS, false, false ) ) // address 0x3C for 128 x 32 38 | { 39 | Serial.println( "SSD1306 allocation failed." ); 40 | for(;;); // if failed, do nothing. 41 | } 42 | 43 | Serial.println( "OLED initialization OK!" ); 44 | OLEDdisplayForOLEDInit(); 45 | 46 | } // initializeOLED() 47 | 48 | 49 | void OLEDdisplayForOLEDInit() 50 | { 51 | display.clearDisplay(); 52 | display.setTextColor(WHITE); 53 | display.setTextSize(1); 54 | display.setCursor(0,0); 55 | display.print("OLED Init. OK!"); 56 | display.display(); 57 | delay(2000); 58 | 59 | } // OLEDdisplayForOLEDInit 60 | 61 | 62 | void OLEDdisplayForSDCardInit() 63 | { 64 | display.setCursor(0,10); 65 | display.print("SD Card Init. OK!"); 66 | display.display(); 67 | delay(2000); 68 | 69 | } // OLEDdisplayForSDCardInit 70 | 71 | 72 | void OLEDdisplayForSPIFFSInit() 73 | { 74 | display.setCursor(0,10); 75 | display.print("SPIFFS Init. OK!"); 76 | display.display(); 77 | delay(2000); 78 | 79 | } // OLEDdisplayForSPIFFSInit 80 | 81 | 82 | void OLEDdisplayForLoRaInit() 83 | { 84 | display.setCursor(0,20); 85 | display.print("LoRa Init. OK!"); 86 | display.display(); 87 | 88 | } // OLEDdisplayForLoRaInit 89 | 90 | 91 | void OLEDdisplayForLoRaSenderInit() 92 | { 93 | display.clearDisplay(); 94 | display.setTextColor(WHITE); 95 | display.setTextSize(1); 96 | display.setCursor(0,0); 97 | display.print("LORA SENDER"); 98 | display.display(); 99 | delay(2000); 100 | 101 | } // OLEDdisplayForLoRaSender 102 | 103 | 104 | void OLEDdisplayForLoRaReceiverInit() 105 | { 106 | display.clearDisplay(); 107 | display.setTextColor(WHITE); 108 | display.setTextSize(1); 109 | display.setCursor(0,0); 110 | display.print("LORA RECEIVER"); 111 | display.display(); 112 | delay(2000); 113 | 114 | } // OLEDdisplayForLoRaReceiverInit 115 | 116 | 117 | void OLEDdisplayForLoRaSender( wifiDeviceData data ) 118 | { 119 | display.clearDisplay(); 120 | display.setTextColor(WHITE); 121 | display.setTextSize(1); 122 | display.setCursor(0,0); 123 | display.print("LoRa packet sent!"); 124 | display.setCursor(0,30); 125 | 126 | #if defined(Slave_1) 127 | display.print("Packet ID:" + String(packetCounter1)); 128 | #elif defined(Slave_2) 129 | display.print("Packet ID:" + String(packetCounter2)); 130 | #elif defined(Slave_3) 131 | display.print("Packet ID:" + String(packetCounter3)); 132 | #elif defined(Slave_4) 133 | display.print("Packet ID:" + String(packetCounter4)); 134 | #endif 135 | 136 | char stringMACaddress[18] = {0}; 137 | MACnumberTostring( stringMACaddress, data.mac ); 138 | 139 | display.setCursor(0,40); 140 | display.print("MAC:"); 141 | display.print( stringMACaddress ); 142 | display.setCursor(0,50); 143 | display.print("RSSI:"); 144 | display.print( data.rssi ); 145 | display.print(" dBm"); 146 | display.display(); 147 | 148 | #if defined(Slave_1) 149 | packetCounter1++; 150 | #elif defined(Slave_2) 151 | packetCounter2++; 152 | #elif defined(Slave_3) 153 | packetCounter3++; 154 | #elif defined(Slave_4) 155 | packetCounter4++; 156 | #endif 157 | 158 | } // OLEDdisplayForLoRaSender 159 | 160 | 161 | void OLEDdisplayForLoRaReceiver( int slaveID ) 162 | { 163 | display.clearDisplay(); 164 | display.setTextColor(WHITE); 165 | display.setTextSize(1); 166 | display.setCursor(0,0); 167 | display.print("Master Device"); 168 | display.setCursor(0,10); 169 | 170 | if (slaveID == 1) 171 | { 172 | display.print("Received packet from Slave #1!"); 173 | } 174 | else if (slaveID == 2) 175 | { 176 | display.print("Received packet from Slave #2!"); 177 | } 178 | else if (slaveID == 3) 179 | { 180 | display.print("Received packet from Slave #3!"); 181 | } 182 | else if (slaveID == 4) 183 | { 184 | display.print("Received packet from Slave #4!"); 185 | } 186 | 187 | display.display(); 188 | 189 | } // OLEDdisplayForLoRaReceiver 190 | 191 | 192 | int getMaxPages() 193 | { 194 | int maxPages = g_espConfigData.number_of_slaves / MAX_MACS_ON_SCREEN; 195 | if ( g_espConfigData.number_of_slaves % MAX_MACS_ON_SCREEN != 0 ) 196 | { 197 | maxPages++; 198 | } 199 | return maxPages; 200 | 201 | } // getMaxPages 202 | 203 | 204 | void OLEDdisplayForSlaveDevicesMac() 205 | { 206 | int currentPage = 1; 207 | int rowIndex = 1; 208 | 209 | for ( size_t i = 0; i < g_espConfigData.number_of_slaves; ++i ) 210 | { 211 | if ( rowIndex == MAX_MACS_ON_SCREEN || currentPage == 1 ) 212 | { 213 | rowIndex = 1; 214 | 215 | delay(2000); 216 | display.clearDisplay(); 217 | display.setTextColor(WHITE); 218 | display.setTextSize(1); 219 | 220 | display.setCursor(0,0); 221 | display.println("Active slaves: "); 222 | 223 | display.setCursor(100,0); 224 | display.println( String(currentPage) + "/" + String(getMaxPages()) ); 225 | currentPage++; 226 | } 227 | 228 | display.setCursor(0, (rowIndex * 10) + 10); 229 | 230 | char forPrintingMac[18] = {0}; 231 | MACnumberTostring( forPrintingMac, g_espConfigData.slave_MACs[i] ); 232 | display.println(forPrintingMac); 233 | display.display(); 234 | 235 | rowIndex++; 236 | } 237 | 238 | delay(2000); 239 | 240 | } // OLEDdisplayForSlaveDevicesMac 241 | 242 | 243 | void OLEDdisplayForWiFiConnection() 244 | { 245 | display.clearDisplay(); 246 | display.setTextColor(WHITE); 247 | display.setTextSize(1); 248 | display.setCursor(0,0); 249 | display.print( "Connecting to " + String(g_espConfigData.ssid) + "..." ); 250 | display.display(); 251 | 252 | } // OLEDdisplayForWiFiConnection 253 | 254 | 255 | void OLEDdisplayForIPAddress() 256 | { 257 | display.clearDisplay(); 258 | display.setTextColor(WHITE); 259 | display.setTextSize(1); 260 | display.setCursor(0,0); 261 | display.print( "Access web server at: " ); 262 | display.setCursor(0,10); 263 | display.print( WiFi.localIP() ); 264 | display.display(); 265 | 266 | } // OLEDdisplayForIPAddress 267 | 268 | 269 | void OLEDdisplayForMasterInit() 270 | { 271 | display.setCursor(0,30); 272 | display.print( "Waiting for data from slaves..." ); 273 | display.display(); 274 | 275 | } // OLEDdisplayForMasterInit 276 | 277 | 278 | void OLEDdisplayForWebServer() 279 | { 280 | OLEDdisplayForIPAddress(); 281 | display.setCursor(0,30); 282 | display.print( "New data logged!" ); 283 | display.setCursor(0,40); 284 | display.print( "#" + String(loggedDataCounter) ); 285 | display.setCursor(0,50); 286 | char time_output[30]; 287 | getTextFormatTime(time_output); 288 | display.print( time_output ); 289 | display.display(); 290 | 291 | } // OLEDdisplayForWebServer -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/src/interactWiFiSniffer.cpp: -------------------------------------------------------------------------------- 1 | /* Header file belonging to this implementation */ 2 | #include "interactWiFiSniffer.hpp" 3 | 4 | /* External heaader files */ 5 | //... 6 | 7 | /* Project header files */ 8 | #include "generalFunctions.hpp" 9 | #include "interactWebServer.hpp" 10 | 11 | 12 | 13 | int g_curChannel = 1; 14 | 15 | 16 | 17 | // Gets the relevant data from the received packets and puts it on the queue for sending. 18 | void sniffForWiFiPacketData( void* buf, wifi_promiscuous_pkt_type_t type ) 19 | { 20 | wifiDeviceData deviceData; 21 | 22 | // Cast the buffer content to wifi_promiscuous_pkt* pointer and assign it to p. 23 | wifi_promiscuous_pkt_t *p = (wifi_promiscuous_pkt_t*) buf; 24 | 25 | // Assign the payload part (Data or management payload) to our custom data type. 26 | WifiMgmtHdr *wh = (WifiMgmtHdr*) p->payload; 27 | 28 | // Get the source MAC address out of the management header from above. 29 | MacAddr mac_add = (MacAddr) wh->sa; 30 | 31 | deviceData.mac = mac_add; // Copy the MAC adress to the "lightweight" datatype defined for sending data. 32 | deviceData.rssi = (p->rx_ctrl).rssi; // Save rssi (dBm) value. 33 | deviceData.timestamp = (p->rx_ctrl).timestamp; // Save timestamp (us) when packet was received. 34 | 35 | // Putting the obtained data to the queue, so the core 1 task can send/print it. 36 | xQueueSend( g_queueBetweenMonitorAndSend, &deviceData, portMAX_DELAY ); 37 | 38 | } // sinffer 39 | 40 | 41 | // Setting up promiscuous mode. esp_wifi_set_promiscuous_rx_cb runs "sniffer" each time a packet is received. 42 | void setWifiPromiscuousMode() 43 | { 44 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); // Use the default initialization config. 45 | esp_wifi_init( &cfg ); // Allocate resorces to WiFi driver, etc, according to the configuration in cfg. 46 | esp_wifi_set_storage( WIFI_STORAGE_RAM ); // Store related data on volatile memory (RAM). 47 | esp_wifi_set_mode( WIFI_MODE_NULL ); // Mode must be NULL otherwise you can't start promiscuous mode. 48 | esp_wifi_start(); // After setups, start wifi. 49 | esp_wifi_set_promiscuous( true ); // Set promiscuous mode. 50 | esp_wifi_set_promiscuous_filter( &cg_filt ); // Set filtered packet types (given in cg_filt). 51 | esp_wifi_set_promiscuous_rx_cb( &sniffForWiFiPacketData ); // Each time a packet is received, the registered callback function will be called (sniffForWiFiPacketData here). 52 | esp_wifi_set_channel( g_curChannel, WIFI_SECOND_CHAN_NONE ); // Set primary channel. No secondary channel considered. 53 | // (Secondary channel is also 20 MHz and in 802.11n it allows for the extension 54 | // of the primary channel from 20 MHz to 40 MHz bandwidth.) 55 | 56 | } // setWifiPromiscuousMode 57 | 58 | 59 | // Change between the wifi channels, so we can monitor traffic on all of them. 60 | void changeWifiChannel() 61 | { 62 | if ( g_curChannel > maxCh ) 63 | { 64 | g_curChannel = 1; 65 | } 66 | 67 | // Set the channel new channel number. 68 | // 2nd parameter: only not NONE, if 2 channels are used together to provide 40 MHz channels 69 | // instead of the normal 20 MHz bandwidth. This extended case is not treated here. 70 | esp_wifi_set_channel( g_curChannel, WIFI_SECOND_CHAN_NONE ); 71 | 72 | // Change channel after some msecs. 73 | // Needs to be small enough so movement of devices can be captured in time with proper resolution. 74 | // (Think on how fast people normally walk. 1 sec/step at most.) 75 | // And at the same time enough time is needed to capture all the data packets. 76 | // If the channel numbers of the nearby APs mostly used by people walking by are known, 77 | // the loop over channels can be narrowed down accordingly from the max 13. 78 | vTaskDelay( 400 / portTICK_PERIOD_MS ); 79 | 80 | g_curChannel++; 81 | 82 | } // changeWifiChannel 83 | 84 | 85 | // Parse WiFi device data to a string vector 86 | std::vector parseWiFiDeviceData( wifiDeviceData& data ) 87 | { 88 | std::vector WiFiDeviceData(3); 89 | std::vector os(2); 90 | 91 | os[0] << data.timestamp; 92 | WiFiDeviceData[0] = os[0].str(); // Timestamp 93 | 94 | // This char array must be able to accomodate a C-string. 95 | // C-strings have a 0 at their ending, so \0 is added to every char array passed to the function. 96 | // Our MAC format contains 2 * 6 = 12 characters for the 6 two digit hex values and 5 colons between them. 97 | // Therefore we need: 12 (MACs) + 5 (colons) + 1 (0 at end) = 18 characters for buffer size. 98 | char forPrintingBSSID[18] = {0}; 99 | MACnumberTostring( forPrintingBSSID, data.mac ); 100 | WiFiDeviceData[1] = forPrintingBSSID; // MAC Address 101 | 102 | os[1] << data.rssi; 103 | WiFiDeviceData[2] = os[1].str(); // RSSI 104 | 105 | return WiFiDeviceData; 106 | 107 | } // parseWiFiDeviceData 108 | 109 | 110 | // Print WiFi device data to serial terminal 111 | void printWiFiDeviceData( std::vector data ) 112 | { 113 | Serial.print( F("Timestamp in usecs: ") ); 114 | Serial.println( data[0].c_str() ); 115 | Serial.print( F("MAC Address: ") ); 116 | Serial.println( data[1].c_str() ); 117 | Serial.print( F("RSSI: ") ); 118 | Serial.print( data[2].c_str() ); 119 | Serial.println( F(" dBm") ); 120 | Serial.println(); 121 | 122 | } // printWiFiDeviceData 123 | 124 | 125 | // Print WiFi device payload to serial terminal 126 | void printWiFiDeviceData( wifiDeviceData& data ) 127 | { 128 | char stringTimestamp[20] = {0}; 129 | timestampTostring( stringTimestamp, (unsigned long) data.timestamp ); 130 | char stringMACaddress[18] = {0}; 131 | MACnumberTostring( stringMACaddress, data.mac ); 132 | 133 | Serial.print( F("Timestamp in usecs: ") ); 134 | Serial.println( stringTimestamp ); 135 | Serial.print( F("MAC Address: ") ); 136 | Serial.println( stringMACaddress ); 137 | Serial.print( F("RSSI: ") ); 138 | Serial.print( data.rssi ); 139 | Serial.println( F(" dBm") ); 140 | Serial.println(); 141 | 142 | } // printWiFiDeviceData 143 | 144 | 145 | // Print WiFi device payload to serial terminal 146 | void printWiFiDevicePayload( wifiDevicePayload& payload ) 147 | { 148 | Serial.print( F("Received packet from ") ); 149 | if (payload.slaveID == 1) 150 | { 151 | Serial.print( F("Slave #1: ") ); 152 | } 153 | else if (payload.slaveID == 2) 154 | { 155 | Serial.print( F("Slave #2: ") ); 156 | } 157 | else if (payload.slaveID == 3) 158 | { 159 | Serial.print( F("Slave #3: ") ); 160 | } 161 | else if (payload.slaveID == 4) 162 | { 163 | Serial.print( F("Slave #4: ") ); 164 | } 165 | Serial.println( payload.packetID ); 166 | 167 | Serial.print( F("MAC Address: ") ); 168 | Serial.println( payload.mac ); 169 | 170 | Serial.print( F("RSSI: ") ); 171 | Serial.print( payload.rssi ); 172 | Serial.println( F(" dBm") ); 173 | 174 | Serial.println(); 175 | 176 | } // printWiFiDeviceData 177 | 178 | 179 | void printCombinedWiFiDeviceData( combinedWiFiDeviceData& combinedData ) 180 | { 181 | Serial.print( F("Last received packet #") ); 182 | Serial.print( String(loggedDataCounter) ); 183 | Serial.print( F(" at: ") ); 184 | Serial.print( combinedData.timestamp ); 185 | Serial.println(); 186 | 187 | Serial.print( F("MAC Address: ") ); 188 | char strMacAddr[18] = {0}; 189 | MACnumberTostring( strMacAddr, combinedData.mac ); 190 | Serial.println( strMacAddr ); 191 | 192 | for ( size_t i = 0; i < g_espConfigData.number_of_slaves; ++i ) 193 | { 194 | Serial.print( F("RSSI ") ); 195 | Serial.print( i + 1 ); 196 | Serial.print( F(" = ") ); 197 | Serial.print( combinedData.rssis[i] ); 198 | Serial.println( F(" dBm") ); 199 | } 200 | 201 | Serial.println(); 202 | 203 | } // printCombinedWiFiDeviceData -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/src/interactSD.cpp: -------------------------------------------------------------------------------- 1 | #if defined(Master) || defined(Slave_1) || defined(Slave_2) 2 | 3 | 4 | /* Header file belonging to this implementation */ 5 | #include "interactSD.hpp" 6 | 7 | /* External heaader files */ 8 | #include 9 | 10 | /* Project header files */ 11 | #include "interactOLED.hpp" 12 | #include "dataTypes.hpp" 13 | #include "generalFunctions.hpp" 14 | #include "globalVariables.hpp" 15 | 16 | 17 | 18 | SPIClass sdSPI; 19 | 20 | 21 | 22 | void initializeSDCard() 23 | { 24 | while (true) // Loop until SD card gets initialized. 25 | { 26 | Serial.println(); 27 | Serial.println( "Initializing SD card..." ); 28 | 29 | vTaskDelay( 1000 / portTICK_PERIOD_MS ); 30 | 31 | sdSPI = SPIClass(HSPI); 32 | sdSPI.begin(SDCARD_SCK, SDCARD_MISO, SDCARD_MOSI, -1); 33 | if ( !SD.begin(SDCARD_CS, sdSPI) ) 34 | { 35 | Serial.println( "ERROR - SD card initialization failed!" ); 36 | continue; 37 | } 38 | 39 | // Check whether SD card is attached. 40 | uint8_t cardType = SD.cardType(); 41 | if ( cardType == CARD_NONE ) 42 | { 43 | Serial.println( "No SD card detected. Please attach SD card!" ); 44 | continue; 45 | } 46 | 47 | break; 48 | } 49 | 50 | Serial.println( "Successfully initialize SD card!" ); 51 | OLEDdisplayForSDCardInit(); 52 | 53 | uint64_t cardSize = SD.cardSize() / (1024 * 1024); 54 | Serial.printf( "SD card Size: %lluMB\n", cardSize ); 55 | 56 | } // initializeSDCard 57 | 58 | 59 | void logSDFileHeader( std::string headerText, const char* path_to_file ) 60 | { 61 | writeFile( SD, path_to_file, headerText.c_str() ); 62 | 63 | } // logSDFileHeader 64 | 65 | 66 | void writeFile( SDFileSystemClass &fs, const char* path, const char* message ) 67 | { 68 | Serial.printf( "Writing file: %s\n", path ); 69 | File file = fs.open(path, FILE_WRITE); 70 | 71 | if(!file) 72 | { 73 | Serial.println( "Failed to open file for writing." ); 74 | return; 75 | } 76 | 77 | if(file.print(message)) 78 | { 79 | Serial.println( "File written." ); 80 | } else { 81 | Serial.println( "Write failed." ); 82 | } 83 | 84 | file.close(); 85 | 86 | } // writeFile 87 | 88 | 89 | void appendFile( SDFileSystemClass &fs, const char* path, const char* message ) 90 | { 91 | // Serial.printf("Appending to file: %s\n", path); 92 | File file = fs.open(path, FILE_APPEND); 93 | 94 | if(!file) 95 | { 96 | Serial.println("Failed to open file for appending."); 97 | return; 98 | } 99 | 100 | if(file.print(message)) 101 | { 102 | //Serial.println("Message appended"); 103 | } else { 104 | Serial.println("Append failed."); 105 | } 106 | 107 | file.close(); 108 | 109 | } // appendFile 110 | 111 | 112 | void logSDCard( String dataMessage, const char* path_to_file ) 113 | { 114 | appendFile(SD, path_to_file, dataMessage.c_str()); 115 | 116 | } // logSDCard 117 | 118 | 119 | void logToCsvFileOnSD( wifiDeviceData& data, const char* path_to_file ) 120 | { 121 | char dataMessage[50] = ""; 122 | 123 | strcat( dataMessage, uint32_to_string( data.timestamp ).c_str() ); 124 | strcat( dataMessage, "," ); 125 | 126 | // This char array must be able to accomodate a C-string. 127 | // C-strings have a 0 at their ending, so \0 is added to every char array passed to the function. 128 | // Our MAC format contains 2 * 6 = 12 characters for the 6 two digit hex values and 5 colons between them. 129 | // Therefore we need: 12 (MACs) + 5 (colons) + 1 (0 at end) = 18 characters for buffer size. 130 | char forPrintingMAC[18] = {0}; 131 | MACnumberTostring( forPrintingMAC, data.mac ); 132 | strcat( dataMessage, forPrintingMAC ); 133 | 134 | strcat( dataMessage, "," ); 135 | 136 | char stringRSSI[5] = {0}; 137 | RSSITostring( stringRSSI, data.rssi ); 138 | strcat( dataMessage, stringRSSI ); 139 | 140 | strcat( dataMessage, "\n" ); 141 | 142 | appendFile(SD, path_to_file, dataMessage); 143 | 144 | } // logToCsvFileOnSD 145 | 146 | 147 | void logToCsvFileOnSD( combinedWiFiDeviceData& data, const char* path_to_file ) 148 | { 149 | char dataMessage[100] = ""; 150 | 151 | strcat( dataMessage, data.timestamp ); 152 | strcat( dataMessage, "," ); 153 | 154 | // This char array must be able to accomodate a C-string. 155 | // C-strings have a 0 at their ending, so \0 is added to every char array passed to the function. 156 | // Our MAC format contains 2 * 6 = 12 characters for the 6 two digit hex values and 5 colons between them. 157 | // Therefore we need: 12 (MACs) + 5 (colons) + 1 (0 at end) = 18 characters for buffer size. 158 | char forPrintingMAC[18] = {0}; 159 | MACnumberTostring( forPrintingMAC, data.mac ); 160 | strcat( dataMessage, forPrintingMAC ); 161 | 162 | for ( int i = 0; i < g_espConfigData.number_of_slaves; i++ ) 163 | { 164 | strcat( dataMessage, "," ); 165 | 166 | char stringRSSI[5] = {0}; 167 | RSSITostring( stringRSSI, data.rssis[i] ); 168 | strcat( dataMessage, stringRSSI ); 169 | } 170 | 171 | strcat( dataMessage, "\n" ); 172 | 173 | appendFile(SD, path_to_file, dataMessage); 174 | 175 | } // logToCsvFileOnSD 176 | 177 | 178 | void readFile( SDFileSystemClass &fs, const char* path ) 179 | { 180 | Serial.printf("Reading file: %s\n", path); 181 | 182 | File file = fs.open(path); 183 | if (!file) 184 | { 185 | Serial.println("Failed to open file for reading."); 186 | return; 187 | } 188 | 189 | Serial.println("Read from file: "); 190 | while (file.available()) 191 | { 192 | Serial.write(file.read()); 193 | } 194 | 195 | } // readFile 196 | 197 | 198 | void listDir( SDFileSystemClass &fs, const char* dirname, uint8_t levels ) 199 | { 200 | Serial.printf("Listing directory: %s\n", dirname); 201 | 202 | File root = fs.open(dirname); 203 | if(!root) 204 | { 205 | Serial.println("Failed to open directory."); 206 | return; 207 | } 208 | if(!root.isDirectory()) 209 | { 210 | Serial.println("Not a directory."); 211 | return; 212 | } 213 | 214 | File file = root.openNextFile(); 215 | while(file) 216 | { 217 | if(file.isDirectory()){ 218 | Serial.print(" DIR : "); 219 | Serial.println(file.name()); 220 | if(levels) 221 | { 222 | listDir( fs, file.name(), levels - 1 ); 223 | } 224 | } else { 225 | Serial.print(" FILE: "); 226 | Serial.print(file.name()); 227 | Serial.print(" SIZE: "); 228 | Serial.println(file.size()); 229 | } 230 | file = root.openNextFile(); 231 | } 232 | 233 | } // listDir 234 | 235 | 236 | void logToBinFileOnSD( wifiDeviceData deviceData, const char* path_to_file ) 237 | { 238 | File binLogFile = SD.open( path_to_file , FILE_APPEND ); 239 | binLogFile.write( (const uint8_t*) &deviceData, sizeof(deviceData) ); 240 | binLogFile.close(); 241 | 242 | } // logBinFileToSD 243 | 244 | 245 | void readBinFileFromSD( wifiDeviceData& deviceData, const char* path_to_file ) 246 | { 247 | File binLogFile = SD.open( path_to_file, FILE_READ ); 248 | binLogFile.read( (uint8_t*) &deviceData, sizeof( wifiDeviceData ) ); 249 | 250 | } // readBinFileFromSD 251 | 252 | 253 | void convertBinToCsvFile( const char* path_to_bin, const char* path_to_csv ) 254 | { 255 | File binLogFile = SD.open( path_to_bin, FILE_READ ); 256 | 257 | // If we have an old version of this file, we should remove it first. 258 | if ( SD.exists( path_to_csv ) ) 259 | { 260 | SD.remove( path_to_csv ); 261 | } 262 | 263 | // Determine the number of recorded wifiDeviceData datapackets in the binary. 264 | uint32_t numOfRecords = ( binLogFile.size() ) / sizeof( wifiDeviceData ); 265 | 266 | wifiDeviceData bufferDeviceData; 267 | 268 | // Add a header at the top of the csv. 269 | logSDFileHeader( "Time [usec],MAC,RSSI [dBm]\n", path_to_csv ); 270 | 271 | // Copy the content of the binary file to the csv file. 272 | for ( size_t i = 0; i < numOfRecords; i++ ) 273 | { 274 | binLogFile.read( (uint8_t*) &bufferDeviceData, sizeof( wifiDeviceData ) ); 275 | logToCsvFileOnSD( bufferDeviceData, path_to_csv ); 276 | } 277 | 278 | } // convertBinToTextFile 279 | 280 | 281 | #endif 282 | 283 | 284 | 285 | 286 | -------------------------------------------------------------------------------- /ESP32WiFiSnifferApp/src/interactFileSystem.cpp: -------------------------------------------------------------------------------- 1 | /* Header file belonging to this implementation */ 2 | #include "interactFileSystem.hpp" 3 | 4 | /* External heaader files */ 5 | #include 6 | #include 7 | 8 | /* Project header files */ 9 | #include "interactSD.hpp" 10 | #include "interactSPIFFS.hpp" 11 | #include "generalFunctions.hpp" 12 | #include "globalVariables.hpp" 13 | #include "dataTypes.hpp" 14 | 15 | 16 | JsonObject getConfigFile( fs::FS &fs, DynamicJsonDocument& jsondoc, const char* path_to_file ) 17 | { 18 | File jsonConfigFile = fs.open( path_to_file ); 19 | if( jsonConfigFile ) 20 | { 21 | DeserializationError error = deserializeJson( jsondoc, jsonConfigFile ); 22 | if( error ) 23 | { 24 | Serial.println( F("Error parsing JSON!") ); 25 | } 26 | return jsondoc.as(); 27 | } 28 | else 29 | { 30 | Serial.println( F("Error opening JSON config file (or it does not exist)!") ); 31 | Serial.println( F("Empty JSON object was returned.") ); 32 | return jsondoc.to(); 33 | } 34 | 35 | } // getConfigFile 36 | 37 | 38 | void printConfigFile( const char* path_to_file ) 39 | { 40 | #if defined(Master) || defined(Slave_1) || defined(Slave_2) 41 | File fileToRead = SD.open(path_to_file); 42 | #elif defined(Slave_3) || defined(Slave_4) 43 | File fileToRead = SPIFFS.open(path_to_file); 44 | #endif 45 | 46 | if( !fileToRead ) 47 | { 48 | Serial.println( F("Failed to read file!") ); 49 | return; 50 | } 51 | 52 | Serial.println( "\n" ); 53 | Serial.println( "------ The content of the config file that was read in ------" ); 54 | Serial.println(); 55 | while ( fileToRead.available() ) 56 | { 57 | Serial.print( (char) fileToRead.read() ); 58 | } 59 | Serial.println(); 60 | Serial.println(); 61 | Serial.println( "---------------- End of config file read in -----------------" ); 62 | Serial.println( "\n" ); 63 | 64 | delay(1000); 65 | 66 | } // printConfigFile 67 | 68 | 69 | void printCurrentConfigData() 70 | { 71 | Serial.println( "\n" ); 72 | Serial.println( "----- Content of the current state of config variables -----" ); 73 | Serial.println(); 74 | 75 | Serial.print( "SSID: " ); 76 | Serial.println( g_espConfigData.ssid ); 77 | Serial.print( "Password: " ); 78 | Serial.println( g_espConfigData.pswd ); 79 | 80 | Serial.println(); 81 | Serial.print( "1m RSSI: " ); 82 | Serial.println( g_espConfigData.rssi_1m ); 83 | 84 | Serial.println(); 85 | Serial.print( "Path loss exponent: " ); 86 | Serial.println( g_espConfigData.path_loss_exp, 4 ); 87 | 88 | Serial.println(); 89 | Serial.print( "Is this a demo: "); 90 | if ( g_espConfigData.demo ) 91 | { 92 | Serial.println( "True" ); 93 | } 94 | else 95 | { 96 | Serial.println( "False" ); 97 | } 98 | 99 | Serial.println(); 100 | Serial.print( "Master device: "); 101 | if ( g_espConfigData.am_i_master ) 102 | { 103 | Serial.println( "True" ); 104 | Serial.print( "Master MAC: " ); 105 | { 106 | // This char array must be able to accomodate a C-string. 107 | // C-strings have a 0 at their ending (null-terminated string), so \0 is added to every char array passed to the function. 108 | // Our MAC format contains 2 * 6 = 12 characters for the 6 two digit hex values and 5 colons between them. 109 | // Therefore we need: 12 (MACs) + 5 (colons) + 1 (0 at end) = 18 characters for buffer size. 110 | char forPrintingMac[18] = {0}; 111 | MACnumberTostring( forPrintingMac, g_espConfigData.master_MAC ); 112 | Serial.println( forPrintingMac ); 113 | } 114 | } 115 | else 116 | { 117 | Serial.println( "False" ); 118 | } 119 | 120 | Serial.println(); 121 | Serial.print( "Number of slaves: "); 122 | Serial.println( g_espConfigData.number_of_slaves ); 123 | 124 | Serial.println(); 125 | Serial.println( "Slave MACs: " ); 126 | for ( int i = 0; i < g_espConfigData.number_of_slaves; i++ ) 127 | { 128 | // This char array must be able to accomodate a C-string. 129 | // C-strings have a 0 at their ending, so \0 is added to every char array passed to the function. 130 | // Our MAC format contains 2 * 6 = 12 characters for the 6 two digit hex values and 5 colons between them. 131 | // Therefore we need: 12 (MACs) + 5 (colons) + 1 (0 at end) = 18 characters for buffer size. 132 | char forPrintingMac[18] = {0}; 133 | MACnumberTostring( forPrintingMac, g_espConfigData.slave_MACs[i] ); 134 | Serial.println( forPrintingMac ); 135 | } 136 | Serial.println(); 137 | 138 | Serial.println( "X Coordinates of slaves: " ); 139 | for ( int i = 0; i < g_espConfigData.number_of_slaves; i++ ) 140 | { 141 | Serial.print( g_espConfigData.slave_x_coords[i], 2 ); 142 | Serial.print( " " ); 143 | } 144 | Serial.println(); 145 | 146 | Serial.println(); 147 | Serial.println( "Y Coordinates of slaves: " ); 148 | for ( int i = 0; i < g_espConfigData.number_of_slaves; i++ ) 149 | { 150 | Serial.print( g_espConfigData.slave_y_coords[i], 2 ); 151 | Serial.print( " " ); 152 | } 153 | Serial.println(); 154 | 155 | Serial.println(); 156 | Serial.print( "MAC of photo device: " ); 157 | { 158 | // This char array must be able to accomodate a C-string. 159 | // C-strings have a 0 at their ending, so \0 is added to every char array passed to the function. 160 | // Our MAC format contains 2 * 6 = 12 characters for the 6 two digit hex values and 5 colons between them. 161 | // Therefore we need: 12 (MACs) + 5 (colons) + 1 (0 at end) = 18 characters for buffer size. 162 | char forPrintingMac[18] = {0}; 163 | MACnumberTostring( forPrintingMac, g_espConfigData.photo_device_MAC ); 164 | Serial.println( forPrintingMac ); 165 | } 166 | Serial.println(); 167 | 168 | Serial.println( "-------------------- End of config info --------------------" ); 169 | Serial.println( "\n" ); 170 | 171 | delay(1000); 172 | 173 | } // printCurrentConfigData 174 | 175 | 176 | void configESPfromJSON( const char* path_to_file ) 177 | { 178 | // To calculate the size of JSON in bytes: https://arduinojson.org/v6/assistant/ 179 | DynamicJsonDocument configDynJsonDoc( 2048 ); 180 | JsonObject configJsonObj; 181 | 182 | #if defined(Master) || defined(Slave_1) || defined(Slave_2) 183 | configJsonObj = getConfigFile( SD, configDynJsonDoc, path_to_file ); 184 | #elif defined(Slave_3) || defined(Slave_4) 185 | configJsonObj = getConfigFile( SPIFFS, configDynJsonDoc, path_to_file ); 186 | #endif 187 | strcpy( g_espConfigData.ssid, configJsonObj["ssid"] ); 188 | strcpy( g_espConfigData.pswd, configJsonObj["pswd"] ); 189 | g_espConfigData.am_i_master = configJsonObj["am_i_master"]; 190 | g_espConfigData.rssi_1m = configJsonObj["RSSI_1m"]; 191 | g_espConfigData.path_loss_exp = configJsonObj["path_loss_exp"]; // path loss exponent measured for esp in room, free-line-of-sight. 192 | g_espConfigData.demo = configJsonObj["demo"]; 193 | 194 | // get MAC address of each ESP32 device and assign it to the config file 195 | #if defined(Master) 196 | configJsonObj["master_MAC"] = WiFi.macAddress(); 197 | #elif defined(Slave_1) 198 | configJsonObj["slave_MACs"][0] = WiFi.macAddress(); 199 | #elif defined(Slave_2) 200 | configJsonObj["slave_MACs"][1] = WiFi.macAddress(); 201 | #elif defined(Slave_3) 202 | configJsonObj["slave_MACs"][2] = WiFi.macAddress(); 203 | #elif defined(Slave_4) 204 | configJsonObj["slave_MACs"][3] = WiFi.macAddress(); 205 | #endif 206 | 207 | const char* bufMasterDevice = configJsonObj["master_MAC"]; 208 | stringToMACnumber( bufMasterDevice, g_espConfigData.master_MAC ); 209 | 210 | g_espConfigData.master_coords[0] = configJsonObj["master_coords"]["x"]; 211 | g_espConfigData.master_coords[1] = configJsonObj["master_coords"]["y"]; 212 | 213 | g_espConfigData.number_of_slaves = configJsonObj["number_of_slaves"]; 214 | 215 | const char* bufPhotoDevice = configJsonObj["photo_device_MAC"]; 216 | stringToMACnumber( bufPhotoDevice, g_espConfigData.photo_device_MAC ); 217 | 218 | for (uint8_t i = 0; i < g_espConfigData.number_of_slaves; i++) 219 | { 220 | const char* buf = configJsonObj["slave_MACs"][i]; 221 | stringToMACnumber( buf, g_espConfigData.slave_MACs[i] ); 222 | 223 | g_espConfigData.slave_x_coords[i] = configJsonObj["slave_coords"][i]["x"]; 224 | g_espConfigData.slave_y_coords[i] = configJsonObj["slave_coords"][i]["y"]; 225 | } 226 | 227 | delay(1000); 228 | 229 | } // configESPfromJSON --------------------------------------------------------------------------------