├── data ├── data.json ├── uPlot.min.css ├── trilaterate.html ├── index.html └── uPlot.iife.min.js ├── example_plot_01.png ├── motionDetector_simple.ino ├── test ├── data │ └── index.html ├── motionSensorSimple.ino ├── motionSensorProbe.ino ├── motionSensorWeb.ino ├── trilaterate.html └── Motion_Ping_Distance.ino ├── README.md ├── motionDetector ├── motionDetector.h └── motionDetector.cpp ├── Motion_simple_no_library.ino ├── probeRequest.ino ├── motionDetector.ino └── LICENSE /data/data.json: -------------------------------------------------------------------------------- 1 | [7,"Epoch","MLevel","RLevel","Father","Mother","Son","Daughter"] 2 | -------------------------------------------------------------------------------- /example_plot_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/happytm/MotionDetector/HEAD/example_plot_01.png -------------------------------------------------------------------------------- /data/uPlot.min.css: -------------------------------------------------------------------------------- 1 | .uplot, .uplot *, .uplot *::before, .uplot *::after {box-sizing: border-box;}.uplot {font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";line-height: 1.5;width: min-content;}.u-title {text-align: center;font-size: 18px;font-weight: bold;}.u-wrap {position: relative;user-select: none;}.u-over, .u-under {position: absolute;}.u-under {overflow: hidden;}.uplot canvas {display: block;position: relative;width: 100%;height: 100%;}.u-legend {font-size: 14px;margin: auto;text-align: center;}.u-inline {display: block;}.u-inline * {display: inline-block;}.u-inline tr {margin-right: 16px;}.u-legend th {font-weight: 600;}.u-legend th > * {vertical-align: middle;display: inline-block;}.u-legend .u-marker {width: 1em;height: 1em;margin-right: 4px;background-clip: content-box !important;}.u-inline.u-live th::after {content: ":";vertical-align: middle;}.u-inline:not(.u-live) .u-value {display: none;}.u-series > * {padding: 4px;}.u-series th {cursor: pointer;}.u-legend .u-off > * {opacity: 0.3;}.u-select {background: rgba(0,0,0,0.07);position: absolute;pointer-events: none;}.u-cursor-x, .u-cursor-y {position: absolute;left: 0;top: 0;pointer-events: none;will-change: transform;z-index: 100;}.u-hz .u-cursor-x, .u-vt .u-cursor-y {height: 100%;border-right: 1px dashed #607D8B;}.u-hz .u-cursor-y, .u-vt .u-cursor-x {width: 100%;border-bottom: 1px dashed #607D8B;}.u-cursor-pt {position: absolute;top: 0;left: 0;border-radius: 50%;pointer-events: none;will-change: transform;z-index: 100;/*this has to be !important since we set inline "background" shorthand */background-clip: content-box !important;}.u-select.u-off, .u-cursor-x.u-off, .u-cursor-y.u-off, .u-cursor-pt.u-off {display: none;} -------------------------------------------------------------------------------- /motionDetector_simple.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "motionDetector.h" // Thanks to https://github.com/paoloinverse/motionDetector_esp 3 | 4 | const char* ssid = "ESP"; 5 | const char* password = ""; 6 | int enableCSVgraphOutput = 1; // 0 disable, 1 enable.If enabled, you may use Tools-> Serial Plotter to plot the variance output for each transmitter. 7 | int motionLevel = -1; 8 | int motionThreshold = 15; // Threshhold above which motion is considered detected. 9 | 10 | void setup(){ 11 | Serial.begin(115200); 12 | Serial.println(); 13 | 14 | motionDetector_init(); // initializes the storage arrays in internal RAM 15 | motionDetector_config(64, 16, 3, 3, false); 16 | Serial.setTimeout(1000); 17 | 18 | if (WiFi.waitForConnectResult() != WL_CONNECTED) {Serial.println("Wifi connection failed");WiFi.disconnect(false);delay(1000);WiFi.begin(ssid, password);} 19 | 20 | } // End of setup. 21 | 22 | void loop() 23 | { 24 | motionDetector_set_minimum_RSSI(-80); // Minimum RSSI value to be considered reliable. Default value is 80 * -1 = -80. 25 | motionLevel = 0; // Reset motionLevel to 0 to resume motion tracking. 26 | motionLevel = motionDetector_esp(); // if the connection fails, the motion detector will automatically try to switch to different operating modes by using ESP32 specific calls. 27 | //Serial.print("Motion detected & motion level is: ");Serial.println(motionLevel); 28 | 29 | if (motionLevel > motionThreshold) // Adjust the sensitivity of motion sensor.Higher the number means less sensetive motion sensor is. 30 | {Serial.print("Motion detected & motion level is: ");Serial.println(motionLevel);} 31 | 32 | if (enableCSVgraphOutput > 0) // Use Tools->Serial Plotter to graph the live data! 33 | {motionDetector_enable_serial_CSV_graph_data(enableCSVgraphOutput);} // output CSV data only 34 | 35 | delay(500); 36 | } // End of loop. 37 | -------------------------------------------------------------------------------- /test/data/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ESP32 Motion Detector 6 | 12 | 13 | 14 | 15 | 16 |

ESP32 Motion Detector

17 |
18 | 19 |
20 | 21 |
22 | 23 |

24 | 25 |
26 | 27 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Motion sensor fully based on the ESP32. 3 | 4 | #### Based on excellent work by paoloinverse at: https://github.com/paoloinverse/bistatic_interference_radar_esp 5 | - His library was modified to remove serial debug & serial parameter setting via serial to replace with settings via web interface. 6 | 7 | #### Similar projects: 8 | - https://web.ece.ucsb.edu/~ymostofi/papers/Mobisys21_KoranyMostofi.pdf 9 | - https://neuton.ai/news/projects/75-tabular-data-vs-computer-vision-detecting-room-occupancy.html 10 | - https://github.com/happytm/arduino-room-occupancy 11 | 12 | #### To play with the sensor: 13 | - Simply copy motionDetector folder to your Arduino library folder and compile motionDetector.ino file in Arduino IDE. 14 | - Upload data folder to ESP32 device. 15 | - Connect to AP called "ESP" and point your browser to 192.168.4.1. 16 | - Enter your WiFi settings first (ESP32 will reboot) and refresh the browser then enter motion sensor settings at the web interface. 17 | - Enjoy the graph build up with motion sensor data over time. 18 | 19 | Do you really need to connect your ESP32 SoC to external movement detector sensors such as infrared PIR, ultrasonic or dedicated radar sensors, when the ESP32 itself can work as a passive radar *without any additional hardware* ? 20 | 21 | 22 | Use the ESP32 wifi 802.11 radio as a bistatic radar based on multipath interference. Will detect human intruders when they cross the path between the ESP32 and other access points or wifi clients or freely move inside rooms / buildings at a reasonable distance from the ESP32. 23 | 24 | Refer to the included .ino example, the basic usage is pretty simple. Feel free to experiment, this code works pretty well inside buildings. 25 | 26 | You will notice the strongest variations are when an intruder crosses the line-of-sight path between the ESP32 and the other access point or client used as transmitter. However, appreciable variations will be detected when indirect / reflected paths are crossed! 27 | 28 | The example code outputs the signal over at built in web interface. Web interface also allows to set WiFi & motion sensor settings. 29 | Most of the code is fully automated and preconfigured, although the core parameters can be modified by editing the .h file. 30 | 31 | Commented example plot follows: 32 | 33 | ![example_plot_01](https://user-images.githubusercontent.com/62485162/146927658-b540635e-16f6-4b56-b713-32469a1c8256.png) 34 | 35 | This sensor is possible by a relatively complex digital filter entirely built in integer math, with a low computational expense. 36 | 37 | Please note the code is mostly self-configuring and can autonomously take care of the common problems and failures typically encountered in a wifi based infrastructure, incuding faults affecting the nearby access points and stations, depending on the ESP32 operating mode. 38 | 39 | That being said, feel free to mess with the library internal parameters (such as buffer and filter sizes): if you find anything interesting and worth of notice, I'd be pleased to discuss it with you. 40 | -------------------------------------------------------------------------------- /motionDetector/motionDetector.h: -------------------------------------------------------------------------------- 1 | // Implementation of the bistatic / multistatic interference radar concept: any object moving inside the field between two wifi radios modifies the propagation paths and therefore induces a variation in the RSSI values of each. The purpose of this library is to filter and detect such variations. 2 | // Warning: the bistatic version requires wifi supplicant and access point to be connected. 3 | // The multistatic version, requires the presence of one or more access point in range, but no connection is required. 4 | 5 | 6 | // WARNING: YOU'RE SUPPOSED TO HAVE ALREADY INITIALIZED WIFI BEFORE USING THIS LIBRARY (i.e. WiFi.begin() and mode (WIFI_STA or WIFI_STA_AP) have already been set, and WiFi is already connected to an external AP or has one client already connected 7 | 8 | // Note: the library internally uses the standard wifi.h library but can also be made to work without such library if the rssi value is passed as a parameter. 9 | 10 | 11 | 12 | #define MINIMUM_RSSI -80 // in dBm // minimum acceptable RSSI to consider a transmitter valid for processing (too low the RSSI, the results might become meaningless) 13 | 14 | #define ABSOLUTE_RSSI_LIMIT -128 // in dBm // normaly you should never need to touch this value, it's already lower than the physical limits of most radios. 15 | 16 | #define RADAR_SCAN_INTERVAL 2000 // userland define 17 | 18 | int motionDetector_init(); // initializes the storage arrays in internal RAM 19 | 20 | int motionDetector_init_PSRAM(); // initializes the storage arrays in PSRAM (only for ESP32-CAM modules and ESP32-WROVER modules with the onboard SPI RAM chip) 21 | 22 | int motionDetector_deinit(); // deinitializes the storage arrays 23 | 24 | int motionDetector_config(int, int, int, int, bool); // reconfigure the library with new parameters: sample buffer depth (how many samples to store in the circular buffer, mobile average filter size, variance threshold ( >= 0, how much the interference signal deviates from the norm before triggering a detection result, in dBm), variance integrator limit (how many variance samples we cumulate before evaluating the variance threshold level), finally bolean var set to true -> enable autoregressive filtering (default is false -> disable autoregressive filtering) 25 | 26 | int motionDetector_process(int); // receives RSSI signal as parameter, returns the detection level ( < 0 -> error (see ERROR LEVELS section), == 0 -> no detection, > 0 -> detection level in dBm) 27 | 28 | int motionDetector(); // generic version, only works in STA mode; request the RSSI level internally, then process the signal and return the detection level in dBm 29 | 30 | // bistatic version ESP32 ONLY 31 | 32 | int motionDetector_esp(); // ESP32 specific version, uses the ESP API directly and also works in SoftAP mode; request the RSSI level internally, then process the signal and return the detection level in dBm 33 | 34 | // current status: IMPLEMENTED 35 | int motionDetector_enable_serial_CSV_graph_data(int); // [ 0 = disabled, >=1 = enabled (default is 0) ] completely disable the verbose output and only print the variance over the serial console, so that any program listening to the serial can graph the relevant data. 36 | 37 | // current status: IMPLEMENTED 38 | int motionDetector_set_minimum_RSSI(int); 39 | 40 | // current status: IMPLEMENTED 41 | int motionDetector_enable_alarm(int); 42 | 43 | // current status: IMPLEMENTED 44 | int motionDetector_set_alarm_threshold(int); 45 | 46 | // ERROR LEVELS 47 | 48 | #define RADAR_RSSI_TOO_LOW -6 49 | #define WIFI_UNINITIALIZED -5 50 | #define WIFI_MODEINVALID -4 51 | #define RADAR_INOPERABLE -3 52 | #define RADAR_UNINITIALIZED -2 53 | #define RADAR_BOOTING -1 54 | -------------------------------------------------------------------------------- /Motion_simple_no_library.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | const char* ssid = "HTM"; 4 | const char* password = ""; 5 | int sensitivity = 30; // Adujust sensitivity of motion sensor. 6 | int sampleBufSize = 64, AvgSize = 32, varThreshold = 3, varIntegratorLimit = 3; // Tweak according to requirement. 7 | 8 | // ==== MOTION DETECTOR SETTINGS ==== 9 | #define MAX_sampleSize 256 10 | #define MAX_AVERAGEBUFFERSIZE 64 11 | #define MAX_VARIANCE 65535 12 | #define ABSOLUTE_RSSI_LIMIT -100 13 | 14 | int *sampleBuffer = nullptr, *averageBuffer = nullptr, *varianceBuffer = nullptr; 15 | 16 | int sampleSize = MAX_sampleSize; 17 | int averageFilterSize = MAX_sampleSize; 18 | int averageBufferSize = MAX_AVERAGEBUFFERSIZE; 19 | int average = 0; 20 | int averageTemp = 0; 21 | int sampleBufferIndex = 0; 22 | int sampleBufferValid = 0; 23 | int averageBufferIndex = 0; 24 | int averageBufferValid = 0; 25 | int variance = 0; 26 | int variancePrev = 0; 27 | int varianceSample = 0; 28 | int varianceAR = 0; 29 | int varianceIntegral = 0; 30 | int varianceThreshold = 3; 31 | int varianceIntegratorLimitMax = MAX_sampleSize; 32 | int varianceIntegratorLimit = 3; 33 | int varianceBufferSize = MAX_sampleSize; 34 | int detectionLevel = 0; 35 | int varianceBufferIndex = 0; 36 | int varianceBufferValid = 0; 37 | int enableCSVout = 0; 38 | int minimumRSSI = ABSOLUTE_RSSI_LIMIT; 39 | 40 | // ==== SETUP ==== 41 | void setup() { 42 | Serial.begin(115200); 43 | WiFi.begin(ssid, password); delay(100); 44 | Serial.println(WiFi.localIP()); 45 | 46 | //=============Setup motion sensor===================================================================================================// 47 | if (sampleBufSize > MAX_sampleSize) { sampleBufSize = MAX_sampleSize; } sampleSize = sampleBufSize; varianceBufferSize = sampleBufSize; 48 | if (AvgSize > MAX_sampleSize) { AvgSize = MAX_sampleSize; } averageFilterSize = AvgSize; 49 | if (varThreshold > MAX_VARIANCE) { varThreshold = MAX_VARIANCE; } varianceThreshold = varThreshold; 50 | if (varIntegratorLimit > varianceIntegratorLimitMax) { varIntegratorLimit = varianceIntegratorLimitMax; } varianceIntegratorLimit = varIntegratorLimit; 51 | 52 | // Allocate memory based on config 53 | sampleBuffer = (int*)malloc(sizeof(int) * sampleSize); for (int i = 0; i < sampleSize; i++) sampleBuffer[i] = 0; 54 | averageBuffer = (int*)malloc(sizeof(int) * averageBufferSize); for (int i = 0; i < averageBufferSize; i++) averageBuffer[i] = 0; 55 | varianceBuffer = (int*)malloc(sizeof(int) * varianceBufferSize); for (int i = 0; i < varianceBufferSize; i++) varianceBuffer[i] = 0; 56 | //=====================================================================================================================================// 57 | 58 | } 59 | 60 | // ==== LOOP ==== 61 | void loop() { 62 | int rssi = WiFi.RSSI(); 63 | int motion = motionSensor(rssi); 64 | //Serial.print("RSSI: "); Serial.print(rssi); Serial.print(" -> Detection: "); Serial.println(motion); 65 | 66 | if (motion > sensitivity) {Serial.print(" Motion level detected: "); Serial.println(motion);} 67 | delay(100); // Adjust as needed 68 | } 69 | 70 | int motionSensor(int sample) {sampleBuffer[sampleBufferIndex++] = sample; if (sampleBufferIndex >= sampleSize) {sampleBufferIndex = 0; sampleBufferValid = 1;} if (sampleBufferValid) {averageTemp = 0; for (int i = 0; i < averageFilterSize; i++) {int idx = sampleBufferIndex - i; if (idx < 0) idx += sampleSize; averageTemp += sampleBuffer[idx];} average = averageTemp / averageFilterSize; averageBuffer[averageBufferIndex++] = average; if (averageBufferIndex >= averageBufferSize) {averageBufferIndex = 0; averageBufferValid = 1;} varianceSample = (sample - average)*(sample - average); varianceBuffer[varianceBufferIndex++] = varianceSample; if (varianceBufferIndex >= sampleSize) { varianceBufferIndex = 0; varianceBufferValid = 1;} varianceIntegral = 0; for (int i = 0; i < varianceIntegratorLimit; i++) {int idx = varianceBufferIndex - i; if (idx < 0) idx += sampleSize; varianceIntegral += varianceBuffer[idx];} variance = varianceIntegral;} 71 | 72 | return variance; 73 | } 74 | -------------------------------------------------------------------------------- /test/motionSensorSimple.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | const char* ssid = "HTM"; 4 | const char* password = ""; 5 | int sensitivity = 30; // Adujust sensitivity of motion sensor. 6 | int sampleBufSize = 64, AvgSize = 32, varThreshold = 3, varIntegratorLimit = 3; // Tweak according to requirement. 7 | 8 | // ==== MOTION DETECTOR SETTINGS ==== 9 | #define MAX_sampleSize 256 10 | #define MAX_AVERAGEBUFFERSIZE 64 11 | #define MAX_VARIANCE 65535 12 | #define ABSOLUTE_RSSI_LIMIT -100 13 | 14 | int *sampleBuffer = nullptr, *averageBuffer = nullptr, *varianceBuffer = nullptr; 15 | 16 | int sampleSize = MAX_sampleSize; 17 | int averageFilterSize = MAX_sampleSize; 18 | int averageBufferSize = MAX_AVERAGEBUFFERSIZE; 19 | int average = 0; 20 | int averageTemp = 0; 21 | int sampleBufferIndex = 0; 22 | int sampleBufferValid = 0; 23 | int averageBufferIndex = 0; 24 | int averageBufferValid = 0; 25 | int variance = 0; 26 | int variancePrev = 0; 27 | int varianceSample = 0; 28 | int varianceAR = 0; 29 | int varianceIntegral = 0; 30 | int varianceThreshold = 3; 31 | int varianceIntegratorLimitMax = MAX_sampleSize; 32 | int varianceIntegratorLimit = 3; 33 | int varianceBufferSize = MAX_sampleSize; 34 | int detectionLevel = 0; 35 | int varianceBufferIndex = 0; 36 | int varianceBufferValid = 0; 37 | int enableCSVout = 0; 38 | int minimumRSSI = ABSOLUTE_RSSI_LIMIT; 39 | 40 | // ==== SETUP ==== 41 | void setup() { 42 | Serial.begin(115200); 43 | WiFi.begin(ssid, password); delay(100); 44 | Serial.println(WiFi.localIP()); 45 | 46 | //=============Setup motion sensor===================================================================================================// 47 | if (sampleBufSize > MAX_sampleSize) { sampleBufSize = MAX_sampleSize; } sampleSize = sampleBufSize; varianceBufferSize = sampleBufSize; 48 | if (AvgSize > MAX_sampleSize) { AvgSize = MAX_sampleSize; } averageFilterSize = AvgSize; 49 | if (varThreshold > MAX_VARIANCE) { varThreshold = MAX_VARIANCE; } varianceThreshold = varThreshold; 50 | if (varIntegratorLimit > varianceIntegratorLimitMax) { varIntegratorLimit = varianceIntegratorLimitMax; } varianceIntegratorLimit = varIntegratorLimit; 51 | 52 | // Allocate memory based on config 53 | sampleBuffer = (int*)malloc(sizeof(int) * sampleSize); for (int i = 0; i < sampleSize; i++) sampleBuffer[i] = 0; 54 | averageBuffer = (int*)malloc(sizeof(int) * averageBufferSize); for (int i = 0; i < averageBufferSize; i++) averageBuffer[i] = 0; 55 | varianceBuffer = (int*)malloc(sizeof(int) * varianceBufferSize); for (int i = 0; i < varianceBufferSize; i++) varianceBuffer[i] = 0; 56 | //=====================================================================================================================================// 57 | 58 | } 59 | 60 | // ==== LOOP ==== 61 | void loop() { 62 | int rssi = WiFi.RSSI(); 63 | int motion = motionSensor(rssi); 64 | //Serial.print("RSSI: "); Serial.print(rssi); Serial.print(" -> Detection: "); Serial.println(motion); 65 | 66 | if (motion > sensitivity) {Serial.print(" Motion level detected: "); Serial.println(motion);} 67 | delay(100); // Adjust as needed 68 | } 69 | 70 | int motionSensor(int sample) {sampleBuffer[sampleBufferIndex++] = sample; if (sampleBufferIndex >= sampleSize) {sampleBufferIndex = 0; sampleBufferValid = 1;} if (sampleBufferValid) {averageTemp = 0; for (int i = 0; i < averageFilterSize; i++) {int idx = sampleBufferIndex - i; if (idx < 0) idx += sampleSize; averageTemp += sampleBuffer[idx];} average = averageTemp / averageFilterSize; averageBuffer[averageBufferIndex++] = average; if (averageBufferIndex >= averageBufferSize) {averageBufferIndex = 0; averageBufferValid = 1;} varianceSample = (sample - average)*(sample - average); varianceBuffer[varianceBufferIndex++] = varianceSample; if (varianceBufferIndex >= sampleSize) { varianceBufferIndex = 0; varianceBufferValid = 1;} varianceIntegral = 0; for (int i = 0; i < varianceIntegratorLimit; i++) {int idx = varianceBufferIndex - i; if (idx < 0) idx += sampleSize; varianceIntegral += varianceBuffer[idx];} variance = varianceIntegral;} 71 | 72 | return variance; 73 | } 74 | -------------------------------------------------------------------------------- /test/motionSensorProbe.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | const char* ssid = "ESP"; 4 | const char* password = ""; 5 | 6 | int sensitivity = 30; // Adujust sensitivity of motion sensor. 7 | int scantimePerChannel = 30; // Minimum of 20 recommended for stable scan opperation. 8 | int rssi; 9 | int sampleBufSize = 64, AvgSize = 32, varThreshold = 3, varIntegratorLimit = 3; // Tweak according to requirement. 10 | 11 | // ==== MOTION DETECTOR SETTINGS ==== 12 | #define MAX_sampleSize 256 13 | #define MAX_AVERAGEBUFFERSIZE 64 14 | #define MAX_VARIANCE 65535 15 | #define ABSOLUTE_RSSI_LIMIT -100 16 | 17 | int *sampleBuffer = nullptr, *averageBuffer = nullptr, *varianceBuffer = nullptr; 18 | int sampleSize = MAX_sampleSize; 19 | int averageFilterSize = MAX_sampleSize; 20 | int averageBufferSize = MAX_AVERAGEBUFFERSIZE; 21 | int average = 0; 22 | int averageTemp = 0; 23 | int sampleBufferIndex = 0; 24 | int sampleBufferValid = 0; 25 | int averageBufferIndex = 0; 26 | int averageBufferValid = 0; 27 | int variance = 0; 28 | int variancePrev = 0; 29 | int varianceSample = 0; 30 | int varianceAR = 0; 31 | int varianceIntegral = 0; 32 | int varianceThreshold = 3; 33 | int varianceIntegratorLimitMax = MAX_sampleSize; 34 | int varianceIntegratorLimit = 3; 35 | int varianceBufferSize = MAX_sampleSize; 36 | int detectionLevel = 0; 37 | int varianceBufferIndex = 0; 38 | int varianceBufferValid = 0; 39 | int enableCSVout = 0; 40 | int minimumRSSI = ABSOLUTE_RSSI_LIMIT; 41 | 42 | 43 | // ==== SETUP ==== 44 | void setup() { 45 | Serial.begin(115200); 46 | 47 | WiFi.mode(WIFI_STA); delay(100); 48 | 49 | //=============Setup motion sensor===================================================================================================// 50 | if (sampleBufSize > MAX_sampleSize) { sampleBufSize = MAX_sampleSize; } sampleSize = sampleBufSize; varianceBufferSize = sampleBufSize; 51 | if (AvgSize > MAX_sampleSize) { AvgSize = MAX_sampleSize; } averageFilterSize = AvgSize; 52 | if (varThreshold > MAX_VARIANCE) { varThreshold = MAX_VARIANCE; } varianceThreshold = varThreshold; 53 | if (varIntegratorLimit > varianceIntegratorLimitMax) { varIntegratorLimit = varianceIntegratorLimitMax; } varianceIntegratorLimit = varIntegratorLimit; 54 | 55 | // Allocate memory based on config 56 | sampleBuffer = (int*)malloc(sizeof(int) * sampleSize); for (int i = 0; i < sampleSize; i++) sampleBuffer[i] = 0; 57 | averageBuffer = (int*)malloc(sizeof(int) * averageBufferSize); for (int i = 0; i < averageBufferSize; i++) averageBuffer[i] = 0; 58 | varianceBuffer = (int*)malloc(sizeof(int) * varianceBufferSize); for (int i = 0; i < varianceBufferSize; i++) varianceBuffer[i] = 0; 59 | //=====================================================================================================================================// 60 | 61 | } 62 | 63 | // ==== LOOP ==== 64 | void loop() { 65 | 66 | startWiFiScan(); if ( WiFi.scanComplete() > 0) { printScannedNetworks(WiFi.scanComplete());} 67 | // *** Very important *** there should be no delay in whole loop after this. 68 | 69 | } 70 | 71 | void startWiFiScan() { WiFi.scanNetworks(true, false, true, scantimePerChannel, NULL, ssid); delay(scantimePerChannel * 12);/***very important*** delay of minimum 10 required */ } // (async, show_hidden, passive, max_ms_per_chan, Target_Channel, ssid) 72 | 73 | void printScannedNetworks(uint16_t networksFound) {rssi = WiFi.RSSI(0); Serial.print(networksFound); Serial.println(" probeResponse received. "); 74 | int motion = motionSensor(rssi); 75 | Serial.print("RSSI: "); Serial.print(rssi); Serial.print(" -> Detection: "); Serial.println(motion); 76 | 77 | if (motion > sensitivity) {Serial.print(" Motion level detected: "); Serial.println(motion);} 78 | delay(10); WiFi.scanDelete();} 79 | 80 | int motionSensor(int sample) {sampleBuffer[sampleBufferIndex++] = sample; if (sampleBufferIndex >= sampleSize) {sampleBufferIndex = 0; sampleBufferValid = 1;} if (sampleBufferValid) {averageTemp = 0; for (int i = 0; i < averageFilterSize; i++) {int idx = sampleBufferIndex - i; if (idx < 0) idx += sampleSize; averageTemp += sampleBuffer[idx];} average = averageTemp / averageFilterSize; averageBuffer[averageBufferIndex++] = average; if (averageBufferIndex >= averageBufferSize) {averageBufferIndex = 0; averageBufferValid = 1;} varianceSample = (sample - average)*(sample - average); varianceBuffer[varianceBufferIndex++] = varianceSample; if (varianceBufferIndex >= sampleSize) { varianceBufferIndex = 0; varianceBufferValid = 1;} varianceIntegral = 0; for (int i = 0; i < varianceIntegratorLimit; i++) {int idx = varianceBufferIndex - i; if (idx < 0) idx += sampleSize; varianceIntegral += varianceBuffer[idx];} variance = varianceIntegral;} 81 | 82 | return variance; 83 | } 84 | -------------------------------------------------------------------------------- /test/motionSensorWeb.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | AsyncWebServer server(80); 7 | 8 | const char* ssid = "HTM"; 9 | const char* password = ""; 10 | int sensitivity = 30; // Adujust sensitivity of motion sensor. 11 | int sampleBufSize = 64, AvgSize = 32, varThreshold = 3, varIntegratorLimit = 3; // Tweak according to requirement. 12 | 13 | // ==== MOTION DETECTOR SETTINGS ==== 14 | #define MAX_sampleSize 256 15 | #define MAX_AVERAGEBUFFERSIZE 64 16 | #define MAX_VARIANCE 65535 17 | #define ABSOLUTE_RSSI_LIMIT -100 18 | 19 | int *sampleBuffer = nullptr, *averageBuffer = nullptr, *varianceBuffer = nullptr; 20 | 21 | int sampleSize = MAX_sampleSize; 22 | int averageFilterSize = MAX_sampleSize; 23 | int averageBufferSize = MAX_AVERAGEBUFFERSIZE; 24 | int average = 0; 25 | int averageTemp = 0; 26 | int sampleBufferIndex = 0; 27 | int sampleBufferValid = 0; 28 | int averageBufferIndex = 0; 29 | int averageBufferValid = 0; 30 | int variance = 0; 31 | int variancePrev = 0; 32 | int varianceSample = 0; 33 | int varianceAR = 0; 34 | int varianceIntegral = 0; 35 | int varianceThreshold = 3; 36 | int varianceIntegratorLimitMax = MAX_sampleSize; 37 | int varianceIntegratorLimit = 3; 38 | int varianceBufferSize = MAX_sampleSize; 39 | int detectionLevel = 0; 40 | int varianceBufferIndex = 0; 41 | int varianceBufferValid = 0; 42 | int enableCSVout = 0; 43 | int minimumRSSI = ABSOLUTE_RSSI_LIMIT; 44 | 45 | 46 | // ==== SETUP ==== 47 | void setup() { 48 | Serial.begin(115200); 49 | WiFi.begin(ssid, password); delay(500); 50 | Serial.println(WiFi.localIP()); 51 | 52 | SPIFFS.begin(); 53 | 54 | //=============Setup motion sensor===================================================================================================// 55 | if (sampleBufSize > MAX_sampleSize) { sampleBufSize = MAX_sampleSize; } sampleSize = sampleBufSize; varianceBufferSize = sampleBufSize; 56 | if (AvgSize > MAX_sampleSize) { AvgSize = MAX_sampleSize; } averageFilterSize = AvgSize; 57 | if (varThreshold > MAX_VARIANCE) { varThreshold = MAX_VARIANCE; } varianceThreshold = varThreshold; 58 | if (varIntegratorLimit > varianceIntegratorLimitMax) { varIntegratorLimit = varianceIntegratorLimitMax; } varianceIntegratorLimit = varIntegratorLimit; 59 | 60 | // Allocate memory based on config 61 | sampleBuffer = (int*)malloc(sizeof(int) * sampleSize); for (int i = 0; i < sampleSize; i++) sampleBuffer[i] = 0; 62 | averageBuffer = (int*)malloc(sizeof(int) * averageBufferSize); for (int i = 0; i < averageBufferSize; i++) averageBuffer[i] = 0; 63 | varianceBuffer = (int*)malloc(sizeof(int) * varianceBufferSize); for (int i = 0; i < varianceBufferSize; i++) varianceBuffer[i] = 0; 64 | //=====================================================================================================================================// 65 | 66 | server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html"); 67 | 68 | server.on("/data.json", HTTP_GET, [](AsyncWebServerRequest *request){ 69 | int rssi = WiFi.RSSI(); 70 | int val = motionSensor(rssi); 71 | String json = "{"; 72 | json += "\"rssi\":" + String(rssi) + ", "; 73 | json += "\"variance\":" + String(val); 74 | json += "}"; 75 | request->send(200, "application/json", json); 76 | }); 77 | 78 | server.on("/config.json", HTTP_GET, [](AsyncWebServerRequest *request){ 79 | String json = "{"; 80 | json += "\"threshold\":" + String(varianceThreshold) ; 81 | json += "}"; 82 | request->send(200, "application/json", json); 83 | }); 84 | 85 | server.begin(); 86 | } 87 | 88 | // ==== LOOP ==== 89 | void loop() { 90 | int rssi = WiFi.RSSI(); 91 | int motion = motionSensor(rssi); 92 | //Serial.print("RSSI: "); Serial.print(rssi); Serial.print(" -> Detection: "); Serial.println(motion); 93 | 94 | if (motion > sensitivity) {Serial.print(" Motion level detected: "); Serial.println(motion);} 95 | delay(100); // Adjust as needed 96 | } 97 | 98 | int motionSensor(int sample) {sampleBuffer[sampleBufferIndex++] = sample; if (sampleBufferIndex >= sampleSize) {sampleBufferIndex = 0; sampleBufferValid = 1;} if (sampleBufferValid) {averageTemp = 0; for (int i = 0; i < averageFilterSize; i++) {int idx = sampleBufferIndex - i; if (idx < 0) idx += sampleSize; averageTemp += sampleBuffer[idx];} average = averageTemp / averageFilterSize; averageBuffer[averageBufferIndex++] = average; if (averageBufferIndex >= averageBufferSize) {averageBufferIndex = 0; averageBufferValid = 1;} varianceSample = (sample - average)*(sample - average); varianceBuffer[varianceBufferIndex++] = varianceSample; if (varianceBufferIndex >= sampleSize) { varianceBufferIndex = 0; varianceBufferValid = 1;} varianceIntegral = 0; for (int i = 0; i < varianceIntegratorLimit; i++) {int idx = varianceBufferIndex - i; if (idx < 0) idx += sampleSize; varianceIntegral += varianceBuffer[idx];} variance = varianceIntegral;} 99 | 100 | return variance; 101 | } 102 | -------------------------------------------------------------------------------- /probeRequest.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | WebServer server(80);WiFiServer websocket_underlying_server(81);WiFiServer tcp_server(1883); 8 | 9 | PicoWebsocket::Server<::WiFiServer> websocket_server(websocket_underlying_server);PicoMQTT::Server mqtt(tcp_server, websocket_server); 10 | 11 | int rssi; 12 | 13 | int apChannel = 7; // WiFi Channel for this device. 14 | const int hidden = 0; // If hidden is 1 probe request event handling does not work ? 15 | const char* apSSID = "ESP"; 16 | const char* apPassword = ""; 17 | 18 | 19 | const char webpage[] PROGMEM = R"raw( 20 | 21 | 22 | 23 | 24 | MQTT.js Example 25 | 26 | 27 | 28 | 29 | 30 |

MQTT Example

31 | 51 | 52 | 53 | 54 | 55 | 56 | )raw"; 57 | 58 | void handleNotFound() {String message = "File Not Found\n\n";message += "URI: ";message += server.uri();message += "\nMethod: ";message += (server.method() == HTTP_GET) ? "GET" : "POST";message += "\nArguments: ";message += server.args();message += "\n";for (uint8_t i = 0; i < server.args(); i++) {message += " " + server.argName(i) + ": " + server.arg(i) + "\n";}server.send(404, "text/plain", message);log_i("reply: %s", message.c_str());} 59 | 60 | 61 | // Had to move this function above setup function to compile the sketch successfully. 62 | // See: https://forum.arduino.cc/t/exit-status-1-was-not-declared-in-this-scope-error-message/632717 63 | void probeRequest(WiFiEvent_t event, WiFiEventInfo_t info) 64 | { 65 | int macID[6]; 66 | Serial.print("Probe received from MAC ID : ");for (int i = 0; i < 6; i++) {macID[i] = info.wifi_ap_probereqrecved.mac[i]; Serial.printf("%02X", info.wifi_ap_probereqrecved.mac[i]);if (i < 5)Serial.print(":");}Serial.println(); 67 | for (int i = 0; i < 6; i++) {Serial.println(macID[i], HEX);} 68 | 69 | // This helps reduce interference from unknown devices from far away with weak signals. 70 | if (info.wifi_ap_probereqrecved.rssi > -90) 71 | { 72 | rssi = info.wifi_ap_probereqrecved.rssi; 73 | Serial.print("RSSI: "); Serial.println(rssi); 74 | Serial.print("Connect at IP: ");Serial.print(WiFi.localIP()); Serial.print(" or 192.168.4.1 with connection to ESP AP");Serial.println(" to access the website"); 75 | 76 | char str [256], s [70]; 77 | 78 | sprintf (str, "{"); 79 | sprintf (s, "%i, ", rssi); 80 | strcat (str, s);sprintf (s, "%i:%i:%i:%i:%i:%i", macID[0], macID[1], macID[2], macID[3], macID[4], macID[5]); 81 | strcat (str, s);sprintf (s, "}"); 82 | strcat (str, s); 83 | mqtt.publish("fromServer", str); 84 | 85 | } 86 | } 87 | 88 | void setup() { 89 | Serial.begin(115200); delay(100); 90 | 91 | WiFi.mode(WIFI_AP_STA); 92 | WiFi.begin("HTM", ""); 93 | WiFi.softAP(apSSID, apPassword, apChannel, hidden); 94 | esp_wifi_set_event_mask(WIFI_EVENT_MASK_NONE); // This line is must to activate probe request received event handler. 95 | 96 | Serial.begin(115200); 97 | 98 | mqtt.begin(); 99 | 100 | server.on("/", []() {server.send(200, "text/html", webpage);}); 101 | server.onNotFound(handleNotFound); 102 | server.begin(); 103 | 104 | WiFi.onEvent(probeRequest,WiFiEvent_t::ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED); 105 | Serial.print("Waiting for probe requests ... "); 106 | } 107 | 108 | 109 | void loop() { 110 | 111 | mqtt.subscribe("#", [](const char * payload) {if (payload && strlen(payload)) {Serial.println(payload);Serial.printf("Received message in topic '#' & message is:- %s\n", payload);}}); 112 | 113 | // Publish message with fixed interval 114 | static unsigned long lastPublish = 0;if (millis() - lastPublish >= 6000) { 115 | mqtt.publish("fromServer", "Data from server");lastPublish = millis();} 116 | 117 | mqtt.loop(); 118 | server.handleClient(); 119 | yield(); 120 | } 121 | 122 | -------------------------------------------------------------------------------- /data/trilaterate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Antenna range tester using trilateration and leafletJS 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /test/trilaterate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Antenna range tester using trilateration and leafletJS 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /data/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Motion Sensor, Proberequest, TinyMQTT, Websockets, Asyncwebserver, SPIFFS Editor & uPlot 6 | 7 | 8 | 9 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |

Motion sensor (no hardware sensor required)

28 |

Send Command to ESP32 for WiFi & motion sensor settings

29 | 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |

39 | 40 | 41 | 42 | 43 |

44 | 45 |
46 | 47 | 61 |

Father : Unknown 62 |

Mother : Unknown 63 |

Son : Unknown 64 |

Daughter : Unknown 65 |

Current Motion Level: Sensor calibrating...

66 |

Last motion detected at: Waiting for motion detection...

67 |

Last Motion Level was: Waiting for motion detection...

68 | 69 | 134 | 135 | 136 | 344 | 345 | 346 | 347 | -------------------------------------------------------------------------------- /motionDetector.ino: -------------------------------------------------------------------------------- 1 | #define FIRSTTIME false // Define true if setting up this device for first time. 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "SPIFFS.h" 10 | #include 11 | #include "time.h" 12 | #include // Thanks to https://github.com/hsaturn/TinyMqtt 13 | #include "motionDetector.h" // Thanks to https://github.com/paoloinverse/motionDetector_esp 14 | #include // Thanks to https://github.com/marian-craciunescu/ESP32Ping 15 | 16 | std::string sentTopic = "data"; 17 | std::string receivedTopic = "command"; 18 | 19 | struct tm timeinfo; 20 | #define MY_TZ "EST5EDT,M3.2.0,M11.1.0" //(New York) https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv 21 | 22 | char* room = "Livingroom"; // Needed for person locator.Each location must run probeReceiver sketch to implement person locator. 23 | int rssiThreshold = -50; // Adjust according to signal strength by trial & error. 24 | 25 | IPAddress deviceIP(192, 168, 0, 2); // Fixed IP address assigned to family member's devices to be checked for their presence at home. 26 | //IPAddress deviceIP = WiFi.localIP(); 27 | int device1IP = 2, device2IP = 3, device3IP = 4, device4IP = 5; 28 | uint8_t device1ID[3] = {0xD0, 0xC0, 0x8A}; // First and last 2 bytes of Mac ID of Cell phone #1. 29 | uint8_t device2ID[3] = {0x36, 0x33, 0x33}; // First and last 2 bytes of Mac ID of Cell phone #2. 30 | uint8_t device3ID[3] = {0x36, 0x33, 0x33}; // First and last 2 bytes of Mac ID of Cell phone #3. 31 | uint8_t device4ID[3] = {0x36, 0x33, 0x33}; // First and last 2 bytes of Mac ID of Cell phone #4. 32 | 33 | const char* apSSID = "ESP"; 34 | const char* apPassword = ""; 35 | const int apChannel = 7; 36 | const int hidden = 0; // If hidden is 1 probe request event handling does not work ? 37 | 38 | const char* http_username = "admin"; // Web file editor interface Login. 39 | const char* http_password = "admin"; // Web file editor interface password. 40 | 41 | String dataFile = "/data.json"; // File to store sensor data. 42 | 43 | 44 | //==================User configuration not required below this line ================================================ 45 | 46 | int RSSIlevel, motionLevel = -1; // initial value = -1, any values < 0 are errors, see motionDetector.h , ERROR LEVELS sections for details on how to intepret any errors. 47 | String ssid,password, graphData; 48 | uint8_t receivedCommand[6],showConfig[20]; 49 | const char* ntpServer = "pool.ntp.org"; 50 | unsigned long time_now, epoch, lastDetected; // Epoch time at which last motion level detected above trigger threshold. 51 | 52 | #define MYFS SPIFFS 53 | #define FORMAT_SPIFFS_IF_FAILED true 54 | 55 | MqttBroker broker(1883); 56 | MqttClient myClient(&broker); 57 | 58 | AsyncWebServer webserver(80); 59 | AsyncWebSocket ws("/ws"); 60 | 61 | void notifyClients(String level) { 62 | ws.textAll(level); 63 | Serial.print("Websocket message sent: ");Serial.println(level); 64 | } 65 | 66 | void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) 67 | { 68 | AwsFrameInfo *info = (AwsFrameInfo*)arg; 69 | if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) 70 | { 71 | 72 | // Process incoming message. 73 | data[len] = 0; 74 | String message = ""; 75 | message = (char*)data; 76 | 77 | } 78 | } 79 | 80 | void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) 81 | { 82 | switch (type) { 83 | case WS_EVT_CONNECT: 84 | Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); 85 | break; 86 | case WS_EVT_DISCONNECT: 87 | Serial.printf("WebSocket client #%u disconnected\n", client->id()); 88 | break; 89 | case WS_EVT_DATA: 90 | handleWebSocketMessage(arg, data, len); 91 | break; 92 | case WS_EVT_PONG: 93 | case WS_EVT_ERROR: 94 | break; 95 | } 96 | } 97 | 98 | unsigned long getTime() {time_t now;if (!getLocalTime(&timeinfo)) {Serial.println("Failed to obtain time");return(0);}Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");time(&now);return now;} 99 | 100 | void setup(){ 101 | Serial.begin(115200); 102 | delay(500); 103 | SPIFFS.begin(); 104 | EEPROM.begin(512); 105 | 106 | #if FIRSTTIME 107 | firstTimeSetup(); // Setup device numbers and wifi Channel for remote devices in EEPROM permanantly. 108 | #endif 109 | 110 | EEPROM.readBytes(0, showConfig,20);for(int i=0;i<20;i++){Serial.printf("%d ", showConfig[i]);} 111 | Serial.println(); 112 | 113 | startWiFi(); 114 | startAsyncwebserver(); 115 | 116 | motionDetector_init(); // initializes the storage arrays in internal RAM 117 | motionDetector_config(64, 16, 3, 3, false); 118 | Serial.setTimeout(1000); 119 | 120 | configTime(0, 0, ntpServer); setenv("TZ", MY_TZ, 1); tzset(); // Set environment variable with your time zone 121 | 122 | broker.begin(); 123 | myClient.setCallback(receivedMessage); 124 | myClient.subscribe(receivedTopic); 125 | myClient.subscribe(sentTopic); 126 | 127 | WiFi.onEvent(probeRequest,WiFiEvent_t::ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED); 128 | Serial.print("Waiting for probe requests ... "); 129 | 130 | 131 | } // End of setup 132 | 133 | void loop() 134 | { 135 | 136 | if(millis() >= time_now + (EEPROM.readByte(1) * 10)) 137 | { // Implementation of non blocking delay function. 138 | Serial.print("Motion sensor scan interval: ");Serial.println(EEPROM.readByte(1) * 10); 139 | time_now += (EEPROM.readByte(1) * 10); 140 | 141 | if (EEPROM.readByte(0) == 1) // Only process following code if motion sensor Enabled. 142 | { 143 | Serial.print("Motion sensor Status: "); Serial.println("Enabled"); 144 | Serial.print("Motion sensor minimum RSSI value set to: ");Serial.println(EEPROM.readByte(3) * -1); //motionDetector_set_minimum_RSSI = (EEPROM.readByte(5) * -1); // Minimum RSSI value to be considered reliable. Default value is 80 * -1 = -80. 145 | 146 | notifyClients(String(motionLevel)); 147 | 148 | //Serial.print("Motion sensor Threshold set to: ");Serial.println(EEPROM.readByte(4)); 149 | if (motionLevel > EEPROM.readByte(2)) // If motion is detected. 150 | { 151 | lastDetected = getTime(); 152 | // If motion detected, check any family member is at home. 153 | 154 | Serial.println("Checking if anybody at Home.... "); 155 | 156 | int pingTime, fatherPing, motherPing, sonPing, daughterPing; 157 | 158 | deviceIP[3] = device1IP; 159 | Serial.println("Pinging IP address 2... "); 160 | if(Ping.ping(deviceIP)) {Serial.println("Father is at Home");notifyClients(String(2020));} else { Serial.println("Father is Away");notifyClients(String(2000));} 161 | fatherPing = Ping.averageTime();Serial.print("Ping time in milliseconds: ");Serial.println(fatherPing); 162 | deviceIP[3] = device2IP; 163 | Serial.println("Pinging IP address 3... "); 164 | if(Ping.ping(deviceIP)) {Serial.println("Mother is at Home");notifyClients(String(3030));} else { Serial.println("Mother is Away");notifyClients(String(3000));} 165 | motherPing = Ping.averageTime();Serial.print("Ping time in milliseconds: ");Serial.println(motherPing); 166 | deviceIP[3] = device3IP; 167 | Serial.println("Pinging IP address 4... "); 168 | if(Ping.ping(deviceIP)) {Serial.println("Son is at Home");notifyClients(String(4040));} else { Serial.println("Son is Away");notifyClients(String(4000));} 169 | sonPing = Ping.averageTime();Serial.print("Ping time in milliseconds: ");Serial.println(sonPing); 170 | deviceIP[3] = device4IP; 171 | Serial.println("Pinging IP address 5... "); 172 | if(Ping.ping(deviceIP)) {Serial.println("Daughter is at Home");notifyClients(String(5050));} else { Serial.println("Daughter is Away");notifyClients(String(5000));} 173 | daughterPing = Ping.averageTime();Serial.print("Ping time in milliseconds: ");Serial.println(daughterPing); 174 | 175 | RSSIlevel = WiFi.RSSI(); 176 | 177 | graphData = ",";graphData += lastDetected;graphData += ",";graphData += motionLevel;graphData += ",";graphData += RSSIlevel;graphData += ",";graphData += fatherPing;graphData += ",";graphData += motherPing;graphData += ",";graphData += sonPing;graphData += ",";graphData += daughterPing;graphData += "]"; 178 | 179 | File f = SPIFFS.open(dataFile, "r+"); Serial.print("File size: "); Serial.println(f.size()); // See https://github.com/lorol/LITTLEFS/issues/33 180 | f.seek((f.size()-1), SeekSet);Serial.print("Position: "); Serial.println(f.position()); 181 | f.print(graphData);Serial.println();Serial.print("Appended to file: "); Serial.println(graphData);Serial.print("File size: "); Serial.println(f.size()); 182 | f.close(); 183 | } 184 | notifyClients(String(lastDetected)); 185 | ws.cleanupClients(); 186 | 187 | } else { Serial.print("Motion sensor Status: "); Serial.println("Disabled");} 188 | motionLevel = 0; // Reset motionLevel to 0 to resume motion tracking. 189 | motionLevel = motionDetector_esp(); // if the connection fails, the radar will automatically try to switch to different operating modes by using ESP32 specific calls. 190 | Serial.print("Motion Level: "); 191 | Serial.println(motionLevel); 192 | } 193 | if (WiFi.waitForConnectResult() != WL_CONNECTED) {ssid = EEPROM.readString(270); password = EEPROM.readString(301);Serial.println("Wifi connection failed");Serial.print("Connect to Access Point ");Serial.print(apSSID);Serial.println(" and point your browser to 192.168.4.1 to set SSID and password" );WiFi.disconnect(false);delay(1000);WiFi.begin(ssid.c_str(), password.c_str());} 194 | } // End of loop 195 | 196 | 197 | void probeRequest(WiFiEvent_t event, WiFiEventInfo_t info) 198 | { 199 | Serial.println(); 200 | Serial.print("Probe Received : ");for (int i = 0; i < 6; i++) {Serial.printf("%02X", info.wifi_ap_probereqrecved.mac[i]);if (i < 5)Serial.print(":");}Serial.println(); 201 | Serial.print("Connect at IP: ");Serial.print(WiFi.localIP()); Serial.print(" or 192.168.4.1 with connection to ESP AP");Serial.println(" to monitor and control whole network"); 202 | 203 | if (info.ap_probereqrecved.mac[0] == device1ID[0] && info.wifi_ap_probereqrecved.mac[4] == device1ID[1] && info.wifi_ap_probereqrecved.mac[5] == device1ID[2]) 204 | { // write code to match MAC ID of cell phone to predefined variable and store presence/absense in new variable. 205 | 206 | Serial.println("################ Person 1 arrived ###################### "); 207 | 208 | myClient.publish("Sensordata/Person1/", "Home"); 209 | Serial.print("Signal Strength of device: "); 210 | Serial.println(info.wifi_ap_probereqrecved.rssi); 211 | myClient.publish("Sensordata/Signal/", (String)info.wifi_ap_probereqrecved.rssi); 212 | if (info.wifi_ap_probereqrecved.rssi > rssiThreshold) // Adjust according to signal strength by trial & error. 213 | { // write code to match MAC ID of cell phone to predefined variable and store presence/absense in new variable. 214 | myClient.publish("Sensordata/Person1/in/", room); 215 | } 216 | 217 | } 218 | } // End of Proberequest function. 219 | 220 | void firstTimeSetup() { 221 | 222 | EEPROM.writeByte(0, 0); // Enable/disable motion sensor. 223 | EEPROM.writeByte(1, 100); // Scan interval for motion sensor. 224 | EEPROM.writeByte(2, 100); // Level threshold where motion is considered valid. 225 | EEPROM.writeByte(3, 80); // Minimum RSSI level to be considered for reliable motion detection. 226 | EEPROM.commit(); 227 | } 228 | 229 | void startWiFi() 230 | { 231 | WiFi.mode(WIFI_AP_STA); 232 | 233 | WiFi.softAP(apSSID, apPassword, apChannel, hidden); 234 | esp_wifi_set_event_mask(WIFI_EVENT_MASK_NONE); // This line is must to activate probe request received event handler. 235 | Serial.print("AP started with SSID: ");Serial.println(apSSID); 236 | 237 | ssid = EEPROM.readString(21); password = EEPROM.readString(51); 238 | 239 | WiFi.begin(ssid.c_str(), password.c_str()); 240 | if (WiFi.waitForConnectResult() != WL_CONNECTED) { 241 | Serial.println("Wifi connection failed"); 242 | Serial.print("Connect to Access Point '");Serial.print(apSSID);Serial.println("' and point your browser to 192.168.4.1 to set SSID and password"); 243 | WiFi.disconnect(false); 244 | delay(1000); 245 | WiFi.begin(ssid.c_str(), password.c_str()); 246 | } 247 | Serial.print("Connect at IP: ");Serial.print(WiFi.localIP());Serial.print(" or 192.168.4.1");Serial.print(" If you are connected to AP with SSID: ");Serial.println(apSSID); 248 | 249 | } 250 | 251 | 252 | void startAsyncwebserver() 253 | { 254 | 255 | ws.onEvent(onEvent); 256 | webserver.addHandler(&ws); 257 | 258 | webserver.on("/post", HTTP_POST, [](AsyncWebServerRequest * request){ 259 | 260 | int params = request->params(); 261 | 262 | for(int i=0;igetParam(i); 264 | 265 | String input0 =request->getParam(0)->value();receivedCommand[0] =(atoi(input0.c_str())); 266 | String input1 =request->getParam(1)->value();receivedCommand[1] =(atoi(input1.c_str())); 267 | String input2 =request->getParam(2)->value();receivedCommand[2] =(atoi(input2.c_str())); 268 | String input3 =request->getParam(3)->value();receivedCommand[3] =(atoi(input3.c_str())); 269 | ssid = request->getParam(4)->value().c_str(); 270 | password =request->getParam(5)->value().c_str(); 271 | /* 272 | if(p->isPost()){ 273 | Serial.printf("Command[%s]: %s\n", p->name().c_str(), p->value()); // For debug purpose. 274 | } 275 | */ 276 | } 277 | request -> send(200, "text/plain", "Command received by server successfully, please click browser's back button to get back to main page."); 278 | Serial.print("Command received from Browser: ");Serial.print(receivedCommand[0]);Serial.print(receivedCommand[1]);Serial.print(receivedCommand[2]);Serial.print(receivedCommand[3]);Serial.print(receivedCommand[4]);Serial.println(receivedCommand[5]); 279 | 280 | if (ssid.length() > 0 || password.length() > 0) 281 | { 282 | EEPROM.writeString(21,ssid);EEPROM.writeString(51, password); 283 | EEPROM.commit();Serial.println();Serial.print("Wifi Configuration saved to EEPROM: SSID="); Serial.print(ssid);Serial.print(" & Password="); Serial.println(password);Serial.println("Restarting Gateway now...");delay(1000); 284 | ESP.restart(); 285 | } 286 | for (int i = 0; i < 4; i++) 287 | { 288 | uint8_t motionSettings[4]; // Enable/diable motion sensor, Scan interval, Level threshold & minimum RSSI. 289 | motionSettings[i] = receivedCommand[i]; 290 | EEPROM.writeBytes(0, motionSettings,4); 291 | } 292 | EEPROM.commit(); 293 | EEPROM.readBytes(0, showConfig,10);for(int i=0;i<10;i++){Serial.printf("%d ", showConfig[i]);}Serial.println(); 294 | }); 295 | 296 | 297 | webserver.serveStatic("/", MYFS, "/").setDefaultFile("index.html"); 298 | 299 | webserver.addHandler(new SPIFFSEditor(MYFS, http_username,http_password)); 300 | 301 | webserver.onFileUpload([](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){ 302 | if(!index) 303 | Serial.printf("UploadStart: %s\n", filename.c_str()); 304 | Serial.printf("%s", (const char*)data); 305 | if(final) 306 | Serial.printf("UploadEnd: %s (%u)\n", filename.c_str(), index+len); 307 | }); 308 | 309 | webserver.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){ 310 | if(!index) 311 | Serial.printf("BodyStart: %u\n", total); 312 | Serial.printf("%s", (const char*)data); 313 | if(index + len == total) 314 | Serial.printf("BodyEnd: %u\n", total); 315 | }); 316 | 317 | //Following line must be added before server.begin() to allow local lan request see : https://github.com/me-no-dev/ESPAsyncWebServer/issues/726 318 | DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); 319 | webserver.begin(); 320 | } 321 | 322 | void receivedMessage(const MqttClient* source, const Topic& topic, const char* payload, size_t length) 323 | { 324 | Serial.print("Received message on topic '"); Serial.print(receivedTopic.c_str());Serial.print("' with payload = ");Serial.println(payload); 325 | if (receivedTopic == "command") // Each part of Mqtt commands must be 3 digits (for example: 006 for 6). 326 | { 327 | receivedCommand[0] = atoi(&payload[0]);receivedCommand[1] = atoi(&payload[4]);receivedCommand[2] = atoi(&payload[8]);receivedCommand[3] = atoi(&payload[12]);receivedCommand[4] = atoi(&payload[16]);receivedCommand[5] = atoi(&payload[20]); 328 | Serial.print("Command received via MQTT: ");Serial.print(receivedCommand[0]);Serial.print(receivedCommand[1]);Serial.println(receivedCommand[2]); 329 | } 330 | for (int i = 0; i < 4; i++) 331 | { 332 | uint8_t motionSettings[4]; // Enable/diable motion sensor, Scan interval, Level threshold & minimum RSSI. 333 | motionSettings[i] = receivedCommand[i]; 334 | EEPROM.writeBytes(0, motionSettings,4); 335 | } 336 | EEPROM.commit(); 337 | EEPROM.readBytes(0, showConfig,10);for(int i=0;i<10;i++){Serial.printf("%d ", showConfig[i]);}Serial.println(); 338 | } 339 | -------------------------------------------------------------------------------- /test/Motion_Ping_Distance.ino: -------------------------------------------------------------------------------- 1 | #include // Built in arduino librrary. 2 | #include // Built in arduino librrary. 3 | #include // Built in arduino librrary. 4 | #include // Install from arduino library manager 5 | #include // Built in arduino librrary. 6 | #include "driver/adc.h" // required to turn off ADC. 7 | #include // required to turn off BT. 8 | #include "motionDetector.h" // Thanks to https://github.com/paoloinverse/motionDetector_esp 9 | //#include // Thanks to https://github.com/marian-craciunescu/ESP32Ping 10 | //#include // Built in arduino library. Reference : https://github.com/denyssene/SimpleKalmanFilter 11 | 12 | #define PINGABLE false // If true use ESPPing library to detect presence of known devices. 13 | 14 | #if PINGABLE 15 | #include // Thanks to https://github.com/marian-craciunescu/ESP32Ping 16 | const char* routerSSID = ""; // Main router's SSID.Required to acertain and identify family member's presence. 17 | const char* routerPassword = ""; // Main router's password.Required to acertain and identify family member's presence. 18 | #endif 19 | 20 | 21 | const char* room = "Livingroom"; // Needed for devices locator.Each location must run probeReceiver sketch to implement devices locator. 22 | int rssiThreshold = -50; // Adjust according to signal strength by trial & error. 23 | int motionThreshold = 40; // Adjust the sensitivity of motion sensor.Higher the number means less sensetive motion sensor is. 24 | 25 | int WiFiChannel = 7; // This must be same for all devices on network. 26 | const char* ssid = "ESP"; // Required for OTA update & motion detection. 27 | const char* password = ""; // Required for OTA update & motion detection. 28 | 29 | int enableCSVgraphOutput = 1; // 0 disable, 1 enable.If enabled, you may use Tools-> Serial Plotter to plot the variance output for each transmitter. 30 | int dataInterval; // Interval in minutes to send data. 31 | int pingInterval; // interval in minutes to ping known devices. 32 | int motionLevel = -1; 33 | //int kalmanMotion = 0; 34 | float receivedRSSI = 0; 35 | 36 | /* 37 | #if PINGABLE 38 | IPAddress deviceIP(192, 168, 0, 2); // Fixed IP address assigned to family member's devices to be checked for their presence at home. 39 | //IPAddress deviceIP = WiFi.localIP(); 40 | int device1IP = 2, device2IP = 3, device3IP = 4, device4IP = 5; 41 | #endif //#if PINGABLE 42 | 43 | 44 | uint8_t device1[3] = {0xD0, 0xC0, 0x8A}; // Device1. First and last 2 bytes of Mac ID of Device 1. 45 | uint8_t device2[3] = {0x3C, 0x1C, 0x20}; // Device2. First and last 2 bytes of Mac ID of Device 2. 46 | uint8_t device3[3] = {0x36, 0x33, 0x33}; // Device3. First and last 2 bytes of Mac ID of Device 3. 47 | uint8_t device4[3] = {0x36, 0x33, 0x33}; // Device4. First and last 2 bytes of Mac ID of Device 4. 48 | */ 49 | //==================User configuration generally not required below this line ============================ 50 | 51 | String binFile = "http://192.168.4.1/device_246.bin"; 52 | 53 | int Month, Date, Hour, Minute, Second; // Time synch received from Gateway stored here for further time based automation. More reliable source than internal RTC of local device 54 | 55 | uint8_t showConfig[20]; // Content of EEPROM is saved here. 56 | 57 | int commandType; // digitalwrite, analogwrite, digitalRead, analogRead, neopixel, pin setup etc. 58 | int value1; // gpio pin number or other values like device ID, sleeptime, Ap Channel, Device mode etc. 59 | int value2; // 0 or 1 in case of digitalwrte, 0 to 255 in case of analogwrite or value for RED neopixel or value for sensorType 4. 60 | int value3; // 0 to 255 - value for GREEN neopixel or value for sensorType 5. 61 | int value4; // 0 to 255 - value for BLUE neopixel or value for sensorType 6. 62 | 63 | uint8_t sensorValues[] = // Looks like 24 bytes is minimum (sending as WIFI_IF_AP) and 1500 bytes is maximum limit. 64 | { 65 | 0x80, 0x00, // 0- 1: First byte here must be 80 for Type = Beacon. 66 | 0x00, 0x00, // 2- 3: Can it be used to send more data to gateway? 67 | 0xF6, 0x11, 0x11, 0x11, 0x11, 0x11, // 4- 9: First byte here must be device ID (default F6 for device ID 246).Second byte is voltage value.Fill rest with any 4 types of sensor data. 68 | 0x06, 0x22, 0x22, 0x22, 0x22, 0x22, // 10-15: Unknown device's MAC. 69 | 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, // 16-21: Motion level, unknown device's RSSI, device1 ping time, device2 ping type, device3 ping time, device4 ping time. 70 | 0x00, 0x00, // 22-23: Can it be used to send more data to remote device? 71 | }; 72 | 73 | void setup() { 74 | 75 | 76 | if (EEPROM.readByte(0) == 0 || EEPROM.readByte(0) == 255) {EEPROM.writeByte(0, 246);} 77 | if (EEPROM.readByte(15) < 1 || EEPROM.readByte(15) > 14) {EEPROM.writeByte(15, WiFiChannel);} 78 | EEPROM.writeByte(16, 1); 79 | EEPROM.commit(); 80 | Serial.println("Contents of EEPROM for this device below: "); EEPROM.readBytes(0, showConfig, 19); for (int i = 0; i < 19; i++) {Serial.printf("%d ", showConfig[i]);} 81 | 82 | 83 | motionDetector_init(); motionDetector_config(64, 16, 3, 3, false); Serial.setTimeout(1000); // Initializes the storage arrays in internal RAM and start motion detector with custom configuration. 84 | 85 | WiFi.mode(WIFI_AP_STA); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("Wifi connection failed"); WiFi.disconnect(false); delay(500); WiFi.begin(ssid, password);} 86 | 87 | esp_wifi_set_promiscuous(true); esp_wifi_set_promiscuous_rx_cb(&sniffer); esp_wifi_set_channel(WiFiChannel, WIFI_SECOND_CHAN_NONE); 88 | 89 | Serial.begin(115200); Serial.println("Wait about 1 minute for motion sensor to be ready to detect motion....."); 90 | } 91 | 92 | //===================== End of Setup ==================================================== 93 | 94 | void loop() { 95 | dataInterval++; pingInterval++; 96 | 97 | motionDetector_set_minimum_RSSI(-80); // Minimum RSSI value to be considered reliable. Default value is 80 * -1 = -80. 98 | motionLevel = motionDetector_esp(); // if the connection fails, the radar will automatically try to switch to different operating modes by using ESP32 specific calls. 99 | 100 | Serial.print("Motion detected & motion level is: ");Serial.println(motionLevel); 101 | // Serial.print("Motion detected & motion level is: ");Serial.println(kalmanMotion); 102 | 103 | if (pingInterval > (EEPROM.readByte(16) * 500)) // 500 for 5 minutes. 104 | { 105 | /* 106 | #if PINGABLE 107 | // Connect to main router to ping known devices. 108 | if (WiFi.waitForConnectResult() != WL_CONNECTED) {Serial.println("Wifi connection failed");WiFi.disconnect(false);delay(500);WiFi.begin(routerSSID, routerPassword);} 109 | Serial.println("Checking to see who is at home.... "); 110 | 111 | int pingTime; 112 | 113 | deviceIP[3] = device1IP; Serial.println("Pinging IP address 2... "); if (Ping.ping(deviceIP, 5)) {pingTime = Ping.averageTime();Serial.print("Ping time in milliseconds: ");Serial.println(pingTime);sensorValues[18] = (pingTime);} else {sensorValues[18] = 0;} 114 | deviceIP[3] = device2IP; Serial.println("Pinging IP address 3... "); if (Ping.ping(deviceIP, 5)) {pingTime = Ping.averageTime();Serial.print("Ping time in milliseconds: ");Serial.println(pingTime);sensorValues[19] = (pingTime);} else {sensorValues[19] = 0;} 115 | deviceIP[3] = device3IP; Serial.println("Pinging IP address 4... "); if (Ping.ping(deviceIP, 5)) {pingTime = Ping.averageTime();Serial.print("Ping time in milliseconds: ");Serial.println(pingTime);sensorValues[20] = (pingTime);} else {sensorValues[20] = 0;} 116 | deviceIP[3] = device4IP; Serial.println("Pinging IP address 5... "); if (Ping.ping(deviceIP, 5)) {pingTime = Ping.averageTime();Serial.print("Ping time in milliseconds: ");Serial.println(pingTime);sensorValues[21] = (pingTime);} else {sensorValues[21] = 0;} 117 | 118 | WiFi.disconnect(true); delay(500); // Pinging is done. Disconnect from main router. 119 | WiFi.begin(ssid, password); // Connect to motion detector link AP. 120 | #endif // #if PINGABLE 121 | */ 122 | } 123 | 124 | if (dataInterval > (EEPROM.readByte(16) * 100)) // 100 for 1 minute. 125 | { 126 | sendSensorvalues(); // Send sensor values to gateway at predefined interval (EEPROM.readByte(16)). 127 | } else if (motionLevel > motionThreshold) // Adjust the sensitivity of motion sensor. Higher the number means less sensetive motion sensor is. 128 | { 129 | Serial.print("Motion detected & motion level is: "); Serial.println(motionLevel); 130 | sendSensorvalues(); 131 | } 132 | 133 | delay(600); // Do not change this. Data interval and ping interval is calculated based on this value. 134 | } // End of loop. 135 | 136 | //============= End of main loop and all functions below ==================================== 137 | 138 | void sniffer(void* buf, wifi_promiscuous_pkt_type_t type) 139 | { 140 | 141 | wifi_promiscuous_pkt_t *p = (wifi_promiscuous_pkt_t*)buf; 142 | 143 | if (p->payload[0] == 0x40 ) // HEX 40 for type = Proberequest to filter out unwanted traffic. 144 | { 145 | /* 146 | //Check if any family member (known devices predefined above) came home. 147 | if (p->payload[10] == device1[0] && p->payload[14] == device1[1] && p->payload[15] == device1[2]) { 148 | Serial.print("Device 1 is Home with RSSI : "); Serial.println(p->rx_ctrl.rssi); 149 | } else if (p->payload[10] == device2[0] && p->payload[14] == device2[1] && p->payload[16] == device2[2]) { 150 | Serial.print("Device 2 is Home with RSSI : "); Serial.println(p->rx_ctrl.rssi); 151 | } else if (p->payload[10] == device3[0] && p->payload[14] == device3[1] && p->payload[17] == device3[2]) { 152 | Serial.print("Device 3 is Home with RSSI : "); Serial.println(p->rx_ctrl.rssi); 153 | } else if (p->payload[10] == device4[0] && p->payload[14] == device4[1] && p->payload[15] == device4[2]) { 154 | Serial.print("Device 4 is Home with RSSI : "); Serial.println(p->rx_ctrl.rssi); 155 | 156 | } else { 157 | */ 158 | 159 | if (p->rx_ctrl.rssi > -70) { // Limit filter to nearest devices. Adjust according to area to be monitored. 160 | 161 | Serial.print("Unknown device detected with MAC ID : "); for (int i = 10; i <= 15; i++) { sensorValues[i] = p->payload[i]; Serial.print(sensorValues[i], HEX); } 162 | receivedRSSI = p->rx_ctrl.rssi; 163 | Serial.print(" & RSSI : "); Serial.println(receivedRSSI); 164 | //kalmanFilterRSSI(); // Calculate the estimated value after applying Kalman Filter 165 | } 166 | 167 | // RSSI = -10nlog10(d/d0)+A0 // https://www.wouterbulten.nl/blog/tech/kalman-filters-explained-removing-noise-from-rssi-signals/#fn:2 168 | // https://create.arduino.cc/projecthub/deodates/rssi-based-social-distancing-af0e26 169 | // Following three variables must be float type. 170 | 171 | float RSSI_1meter = -50; // RSSI at 1 meter distance. Adjust according to your environment.Use WiFi Analyser android app from VREM Software & take average of RSSI @ 1 meter. . 172 | float Noise = 2; // Try between 2 to 4. 2 is acceptable number but Adjust according to your environment. 173 | float Distance = pow(10, (RSSI_1meter - receivedRSSI) / (10 * Noise)); Serial.print("Distance: "); Serial.println(Distance); 174 | } 175 | 176 | if (p->payload[0] == 0x80 && p->payload[4] == EEPROM.readByte(0)) // HEX 80 for type = Beacon to filter out unwanted traffic and match device number. 177 | { 178 | Serial.print("Command received from Gateway : "); 179 | 180 | for (int i = 0; i <= 21; i++) { 181 | Serial.print(p->payload[i], HEX); 182 | } 183 | 184 | Serial.println(); 185 | EEPROM.writeByte(1, p->payload[5]); // Command type at EEPROM address 1. 186 | EEPROM.commit(); 187 | 188 | commandType = EEPROM.readByte(1); 189 | 190 | Serial.println("Contents of EEPROM for this device below: "); EEPROM.readBytes(0, showConfig, 19); for (int i = 0; i < 19; i++) {Serial.printf("%d ", showConfig[i]);} 191 | 192 | if ( commandType > 100 && commandType < 121) { // If commandType is 101 to 120. 193 | 194 | Serial.println(); 195 | Serial.print("This device's Wifi Channel is: "); Serial.println(EEPROM.readByte(15)); 196 | Serial.print("This device's MAC ID is: "); Serial.println(WiFi.macAddress()); 197 | 198 | value1 = p->payload[6]; 199 | value2 = p->payload[7]; 200 | value3 = p->payload[8]; 201 | value4 = p->payload[9]; 202 | // New time synch received from Gateway. 203 | Month = p->payload[10]; // January is 0. 204 | Date = p->payload[11]; 205 | Hour = p->payload[12]; 206 | Minute = p->payload[13]; 207 | Second = p->payload[14]; 208 | Serial.print("Time synch received from Gateway: "); Serial.print(Month); Serial.print("/"); Serial.print(Date); Serial.print(" "); Serial.print(Hour); Serial.print(":"); Serial.print(Minute); Serial.print(":"); Serial.println(Second); 209 | 210 | if (commandType == 101) // Digital Write 211 | { 212 | EEPROM.writeByte(2, value1); 213 | EEPROM.writeByte(3, value2); EEPROM.commit(); 214 | Serial.print("Received Command Digital Write: "); Serial.print(value1); Serial.println(value2); 215 | 216 | gpioControl(); 217 | 218 | } else if (commandType == 102) // Analog Write 219 | { 220 | EEPROM.writeByte(4, value1); 221 | EEPROM.writeByte(5, value2); EEPROM.commit(); 222 | Serial.print("Received Command Analog Write: "); Serial.print(value1); Serial.println(value2); 223 | 224 | gpioControl(); 225 | 226 | } else if (commandType == 103) // Digital Read 227 | { 228 | Serial.println("Received Command Digital Read pin: "); Serial.println(value1); 229 | } else if (commandType == 104) // Analog Read 230 | { 231 | Serial.println("Received Command Digital Read pin: "); Serial.println(value1); 232 | } else if (commandType == 105) // Neopixel 233 | { 234 | EEPROM.writeByte(6, value1); 235 | EEPROM.writeByte(7, value2); 236 | EEPROM.writeByte(8, value3); 237 | EEPROM.writeByte(9, value4); EEPROM.commit(); 238 | Serial.print("Received Command Neopixel: "); Serial.print(value1); Serial.println(value2); Serial.print(value3); Serial.println(value4); 239 | 240 | gpioControl(); 241 | 242 | } else if (commandType == 106) // Set Targets 243 | { 244 | EEPROM.writeByte(10, value1); 245 | EEPROM.writeByte(11, value2); 246 | EEPROM.writeByte(12, value3); 247 | EEPROM.writeByte(13, value4); EEPROM.commit(); 248 | Serial.print("Received Command Set Target Values to: "); Serial.println(value1); Serial.println(value2); Serial.print(value3); Serial.println(value4); 249 | 250 | } else if (commandType == 107) // Set AP Channel 251 | { 252 | EEPROM.writeByte(14, value1); EEPROM.commit(); 253 | Serial.print("Received Command Set AP Channel to: "); Serial.println(value1); 254 | 255 | } else if (commandType == 108 && value1 == 1) // Set Mode 256 | { 257 | Serial.print("Received Command Set Device Mode to: "); Serial.println(value1); 258 | EEPROM.writeByte(15, 0); 259 | EEPROM.writeByte(0, 246); EEPROM.commit(); 260 | OTAupdate(); 261 | 262 | } else if (commandType == 109) // Set Sleep Time 263 | { 264 | EEPROM.writeByte(16, value1); EEPROM.commit(); 265 | Serial.print("Received Command Set Sleep Time to: "); Serial.print(value1); Serial.println(" minutes."); 266 | 267 | } else if (commandType == 110) // Set Device ID 268 | { 269 | 270 | EEPROM.writeByte(0, value1); EEPROM.commit(); 271 | Serial.print("Received Command Set Device ID to: "); Serial.println(value1); 272 | 273 | } 274 | 275 | Serial.println("Command from Gateway saved to EEPROM"); 276 | Serial.println("Contents of EEPROM for this device below: "); Serial.println(); 277 | EEPROM.readBytes(0, showConfig, 19); for (int i = 0; i < 19; i++) { 278 | Serial.printf("%d ", showConfig[i]); 279 | } Serial.println(); 280 | delay(1); 281 | } else { 282 | 283 | Serial.println("Resending sensor values..."); 284 | ESP.restart(); // Seems like gateway did not receive sensor values let's try again. 285 | } 286 | } 287 | } 288 | 289 | void sendSensorvalues() 290 | { 291 | sensorValues[4] = EEPROM.readByte(0); // Device ID. 292 | sensorValues[5] = 165; // Voltage must be between 130 and 180 here in whole integer. 293 | sensorValues[6] = random(70, 74); // Sensor 1 value. 294 | sensorValues[7] = random(40, 100); // Sensor 2 value. 295 | sensorValues[8] = random(900, 1024) / 4; // Sensor 3 value. 296 | sensorValues[9] = random(0, 100); // Sensor 4 value. 297 | sensorValues[16] = motionLevel / 10; // Motion Level. 298 | // Values received from all sensors used on this device and should replace random values of sensorValues array. 299 | 300 | Serial.println("Sending sensor values....."); 301 | long lastmillis = millis(); 302 | esp_wifi_80211_tx(WIFI_IF_STA, sensorValues, sizeof(sensorValues), true); 303 | long currentmillis = millis() - lastmillis; 304 | Serial.print("Transmit & receive time (Milliseconds) : "); Serial.println(currentmillis); 305 | 306 | dataInterval = 0; // Data sent. Reset the data interval counter. 307 | } 308 | 309 | 310 | void gpioControl() { 311 | 312 | if ((EEPROM.readByte(2) >= 1 && EEPROM.readByte(2) <= 5) || (EEPROM.readByte(2) >= 12 && EEPROM.readByte(2) <= 39)) 313 | { if (EEPROM.readByte(3) == 1) { 314 | digitalWrite(EEPROM.readByte(2), HIGH); 315 | } else if (EEPROM.readByte(2) == 0) { 316 | digitalWrite(EEPROM.readByte(2), LOW); 317 | } 318 | /* 319 | } else if (commandType == 102){ 320 | analogWrite(EEPROM.readByte(4), EEPROM.readByte(5)); 321 | 322 | } 323 | } 324 | /* 325 | } else if (receivedCommand == 105) { 326 | // TO DO - write function for neopixel 327 | */ 328 | } 329 | } 330 | 331 | void OTAupdate() { // Receive OTA update from bin file on Gateway's LittleFS data folder. 332 | 333 | t_httpUpdate_return ret = ESPhttpUpdate.update(binFile); 334 | 335 | switch (ret) { 336 | case HTTP_UPDATE_FAILED: 337 | Serial.printf("HTTP_UPDATE_FAILD Error (%d): %s", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); 338 | break; 339 | 340 | case HTTP_UPDATE_NO_UPDATES: 341 | Serial.println("HTTP_UPDATE_NO_UPDATES"); 342 | break; 343 | 344 | case HTTP_UPDATE_OK: 345 | Serial.println("HTTP_UPDATE_OK"); 346 | ESP.restart(); 347 | break; 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /motionDetector/motionDetector.cpp: -------------------------------------------------------------------------------- 1 | #include "motionDetector.h" 2 | #include // THIS WILL MAKE THE LIBRARY WORK ONLY IN STA MODE OR AP_STA MODE AS LONG AS YOU'RE CONNECTED AS A STATION 3 | #include "esp_wifi.h" //LIBRARY WORK IN MULTISTATIC MODE IF YOU'VE GOT AT LEAST ONE STATION CONNECTED TO YOUR ESP32's SoftAP. 4 | 5 | #define ENABLE_ALARM_THRESHOLD 0 // if 0, the raw variance signal is returned, if 1, only variance signals above threshold are returned, signals under threshold instead return zero or invalid (-1) 6 | 7 | // internal variables and arrays 8 | 9 | int enableThreshold = ENABLE_ALARM_THRESHOLD; 10 | 11 | bool enableAutoRegressive = false; 12 | 13 | #define MAX_SAMPLEBUFFERSIZE 256 14 | int * sampleBuffer; 15 | int sampleBufferSize = MAX_SAMPLEBUFFERSIZE; // default size, and maximum size // the instant mobile average is calculated from here on mobileAverageFilterSize = n samples 16 | int sampleBufferIndex = 0; 17 | int sampleBufferValid = 0; 18 | 19 | #define MAX_AVERAGEBUFFERSIZE 64 20 | int mobileAverageFilterSize = MAX_SAMPLEBUFFERSIZE; 21 | int mobileAverageBufferSize = MAX_AVERAGEBUFFERSIZE; 22 | int mobileAverage = 0; 23 | int mobileAverageTemp = 0; 24 | int * mobileAverageBuffer; // the instant average value fills this circular buffer of size mobileAverageFilterSize with the mobileAverage values 25 | int mobileAverageBufferIndex = 0; 26 | int mobileAverageBufferValid = 0; 27 | 28 | int bufferIndex = 0; // index used for all buffers. 29 | 30 | //// PLEASE NOTE: the init value is -1, meaning the variance is invalid, it will stay invalid until it can be effectively calculated 31 | // DO NOT CHANGE THE -1 INITIALIZATION VALUE 32 | int variance = RADAR_BOOTING; // this value is calculated from the current sample and the average calculated from the mobileAverageBuffer values 33 | 34 | int variancePrev = 0; 35 | int varianceSample = 0; // deviation of the current sample from the average 36 | int varianceAR = 0; // autoregressive version 37 | int varianceIntegral = 0; 38 | 39 | #define MAX_VARIANCE 65535 40 | int varianceThreshold = 3; // in variance arbitrary units 41 | int varianceIntegratorLimitMax = MAX_SAMPLEBUFFERSIZE; 42 | int varianceIntegratorLimit = 3; 43 | int varianceBufferSize = MAX_SAMPLEBUFFERSIZE; 44 | int *varianceBuffer; // holds the variance values 45 | int detectionLevel = 0; // holds the detected level integrated from the varianceBuffer 46 | int varianceBufferIndex = 0; 47 | int varianceBufferValid = 0; 48 | int enableCSVout = 0; 49 | int minimumRSSI = MINIMUM_RSSI; 50 | 51 | 52 | // functions 53 | 54 | int motionDetector_init() { // initializes the storage arrays in internal RAM 55 | 56 | if (sampleBuffer == NULL) { 57 | sampleBuffer = (int*)malloc(sizeof(int) * sampleBufferSize); 58 | for (int sbtempindex=0; sbtempindex= MAX_SAMPLEBUFFERSIZE) { 129 | sampleBufSize = MAX_SAMPLEBUFFERSIZE; 130 | } 131 | sampleBufferSize = sampleBufSize; 132 | 133 | varianceBufferSize = sampleBufferSize; 134 | 135 | if (mobileAvgSize >= MAX_SAMPLEBUFFERSIZE) { 136 | mobileAvgSize = MAX_SAMPLEBUFFERSIZE; 137 | } 138 | mobileAverageFilterSize = mobileAvgSize; 139 | 140 | if (varThreshold >= MAX_VARIANCE) { 141 | varThreshold = MAX_VARIANCE; 142 | } 143 | varianceThreshold = varThreshold; 144 | 145 | if (varIntegratorLimit >= varianceIntegratorLimitMax) { 146 | varIntegratorLimit = varianceIntegratorLimitMax; 147 | } 148 | varianceIntegratorLimit = varIntegratorLimit; 149 | 150 | enableAutoRegressive = enableAR; 151 | 152 | return 1; 153 | 154 | } 155 | 156 | 157 | int motionDetector_process(int sample = 0) { // send the RSSI signal, returns the detection level ( < 0 -> error, == 0 -> no detection, > 0 -> detection level in variance arbitrary units) 158 | 159 | 160 | if ((sampleBuffer == NULL) || (mobileAverageBuffer == NULL) || (varianceBuffer == NULL)) { 161 | 162 | return RADAR_UNINITIALIZED; // unallocated buffers 163 | } 164 | 165 | if (sample < minimumRSSI) { 166 | 167 | return RADAR_RSSI_TOO_LOW; 168 | } 169 | 170 | sampleBuffer[sampleBufferIndex] = sample; 171 | sampleBufferIndex++; 172 | if ( sampleBufferIndex >= sampleBufferSize ) { // circular buffer, rewinding the index, if the buffer has been filled at least once, then we may start processing valid data 173 | sampleBufferIndex = 0; 174 | sampleBufferValid = 1; 175 | } 176 | 177 | if (sampleBufferValid >= 1) { 178 | // filling in the mobile average data buffer 179 | // the mobile average can be re-calculated even on a full sampleBufferSize set of valid samples, I see no problem in terms of computational load 180 | // calculating the current mobile average now. the sampleBufferIndex points now to the oldest sample 181 | mobileAverageTemp = 0; 182 | int mobilePointer = 0; 183 | for (int mobileAverageSampleIndex = 0; mobileAverageSampleIndex < mobileAverageFilterSize; mobileAverageSampleIndex++) { 184 | mobilePointer = sampleBufferIndex - mobileAverageSampleIndex; 185 | if (mobilePointer <= 0) { 186 | mobilePointer = mobilePointer + (sampleBufferSize -1); 187 | } 188 | mobileAverageTemp = mobileAverageTemp + sampleBuffer[mobilePointer]; 189 | } 190 | mobileAverage = mobileAverageTemp / mobileAverageFilterSize; 191 | // filling in the mobile average buffer with the fresh new value 192 | mobileAverageBuffer[mobileAverageBufferIndex] = mobileAverage; // to be fair, this buffer is filled but still ...really unused. 193 | // truth be said, I'm filling the mobileAverageBuffer for future logging purposes. (TBD) 194 | 195 | // since we have the current mobile average data, we can also extract the current variance data. 196 | // the variable named "variance" at this point still contains the *previous* value of the variance 197 | variancePrev = variance; 198 | // deviation of the current sample 199 | varianceSample = (sample - mobileAverageBuffer[mobileAverageBufferIndex])*(sample - mobileAverageBuffer[mobileAverageBufferIndex]); 200 | // filling in the variance buffer 201 | varianceBuffer[varianceBufferIndex] = varianceSample; 202 | 203 | // the following is a mobile integrator filter that parses the circular buffer called varianceBuffer 204 | varianceIntegral = 0; 205 | int variancePointer = 0; 206 | for (int varianceBufferIndexTemp = 0; varianceBufferIndexTemp < varianceIntegratorLimit; varianceBufferIndexTemp++) { 207 | variancePointer = varianceBufferIndex - varianceBufferIndexTemp; 208 | if (variancePointer <=0) { 209 | variancePointer = variancePointer + (varianceBufferSize -1); 210 | } 211 | varianceIntegral = varianceIntegral + varianceBuffer[variancePointer]; // the full effect of this operation is to make the system more sensitive to continued variations of the RSSI, possibly meaning there's a moving object around the area. 212 | } 213 | // increasing and checking the variance buffer index 214 | varianceBufferIndex++; 215 | if ( varianceBufferIndex >= varianceBufferSize ) { // circular buffer, rewinding the index, if the buffer has been filled at least once, then we may start processing valid data 216 | varianceBufferIndex = 0; 217 | varianceBufferValid = 1; //please note we DO NOT need to have a fully validated buffer to work with the current M.A. data 218 | } 219 | // applying the autoregressive part 220 | varianceAR = (varianceIntegral + varianceAR) / 2; // the effect of this filter is to "smooth" down the signal over time, so it's a simple IIR (infinite impulse response) low pass filter. It makes the system less sensitive to noisy signals, especially those with a deviation of less than 1. 221 | 222 | // assigning the values according to the settings 223 | variance = varianceSample; 224 | 225 | if (enableAutoRegressive) { 226 | variance = varianceAR; 227 | } 228 | if (! enableAutoRegressive) { 229 | variance = varianceIntegral; 230 | } 231 | 232 | // note: we needed to point to the current mobile average data for future operations, so we increase the MA buffer index only as the last step 233 | mobileAverageBufferIndex++; 234 | if ( mobileAverageBufferIndex >= mobileAverageBufferSize ) { // circular buffer, rewinding the index, if the buffer has been filled at least once, then we may start processing valid data 235 | mobileAverageBufferIndex = 0; 236 | mobileAverageBufferValid = 1; //please note we DO NOT need to have a fully validated buffer to work with the current M.A. data 237 | } 238 | 239 | } 240 | 241 | 242 | // final check to determine if the detected variance signal is above the detection threshold, this is only done if enableThreshold > 0 243 | if ((variance >= varianceThreshold) && (enableThreshold > 0)) { 244 | detectionLevel = variance; 245 | 246 | return detectionLevel; 247 | } 248 | // variance signal under threshold, but otherwise valid? 249 | if ((variance < varianceThreshold) && (variance >= 0) && (enableThreshold > 0) ) { 250 | detectionLevel = 0; 251 | 252 | return detectionLevel; 253 | } 254 | 255 | return variance; // if the sample buffer is still invalid at startup, an invalid value is returned: -1, else the raw variance signal is returned 256 | } 257 | 258 | 259 | 260 | 261 | 262 | 263 | int motionDetector() { // request the RSSI level internally, then process the signal and return the detection level in variance arbitrary units 264 | 265 | int RSSIlevel = 0; 266 | int res = 0; 267 | 268 | RSSIlevel = (int)WiFi.RSSI(); // I know, this is lame, but will make this library work with most Arduino-supported devices 269 | 270 | // if the RSSI is zero, then we are most probably not connected to an upstream AP. 271 | 272 | if (RSSIlevel == 0) { 273 | 274 | return RADAR_INOPERABLE; // radar inoperable 275 | } 276 | 277 | res = motionDetector_process(RSSIlevel); 278 | 279 | return res; 280 | 281 | } 282 | 283 | // bistatic version ESP32 ONLY 284 | 285 | #define SCANMODE_STA 0 286 | #define SCANMODE_SOFTAP 1 287 | #define SCANMODE_WIFIPROBE 2 288 | 289 | int scanMode = SCANMODE_STA; // 0 = STA RSSI (SCANMODE_STA); 1 = SoftAP client scan (SCANMODE_SOFTAP); 2 = active wifi scan (SCANMODE_WIFIPROBE); 290 | 291 | uint8_t strongestClientBSSID[6] = {0}; 292 | int strongestClientRSSI = -100; 293 | 294 | int strongestClientfound = 0; // if found set to 1 295 | 296 | int modeRes; // wifi mode reporting variable, initialized when radar operations are requested, and shared with other subfunctions 297 | 298 | uint8_t BSSIDinUse[6] = {0}; // this array identifies the currently used BSSID 299 | 300 | 301 | int bistatic_get_rssi_SoftAP_strongestClient() { 302 | int rssi = 0; 303 | //int scanRes = 0; 304 | 305 | uint8_t * currentBSSID; 306 | int currentRSSI = 0; 307 | wifi_sta_list_t stationList; 308 | esp_err_t scanRes = esp_wifi_ap_get_sta_list(&stationList); 309 | 310 | if (scanRes != ESP_OK) { 311 | return 0; 312 | } 313 | 314 | // extracting the strongest signal (we need to mark it for subsequent scans, in order to build reliable stats) // this is executed one time when strongestClientfound == 0 315 | if (strongestClientfound == 0) { 316 | strongestClientRSSI = -100; 317 | for (int netItem = 0; netItem < stationList.num; netItem++) { 318 | wifi_sta_info_t station = stationList.sta[netItem]; 319 | currentBSSID = station.mac; 320 | currentRSSI = station.rssi; 321 | 322 | if ((currentRSSI > -100) || (currentRSSI < 0)) { 323 | if (currentRSSI > strongestClientRSSI) { 324 | strongestClientfound = 1; 325 | strongestClientRSSI = currentRSSI; 326 | 327 | memcpy ( strongestClientBSSID, currentBSSID, (sizeof(uint8_t) * 6)); 328 | 329 | } 330 | } 331 | } 332 | } 333 | 334 | int bssidScanOK = 0; 335 | if (strongestClientfound == 1) { // looking for the specific BSSID, if not found then reset strongestAPfound to zero and return an invalid RSSI set to 0 336 | for (int netItem = 0; netItem < stationList.num; netItem++) { // parse all the network items found, look for the known strongest BSSID 337 | wifi_sta_info_t station = stationList.sta[netItem]; 338 | currentBSSID = station.mac; 339 | for (int bssidIndex = 0; bssidIndex < 6; bssidIndex++) { // compare with the strongest BSSID, byte by byte 340 | if (currentBSSID[bssidIndex] == strongestClientBSSID[bssidIndex]) { 341 | bssidScanOK = 1; 342 | } else { 343 | bssidScanOK = 0; // match failed, resetting the flag 344 | break; // no matter wich single byte failed to match, we break out of the compare loop 345 | } 346 | } 347 | if (bssidScanOK == 1) { 348 | currentRSSI = station.rssi; 349 | 350 | rssi = currentRSSI; // preparing the final result 351 | break; // exit the scan loop 352 | } 353 | } 354 | if (bssidScanOK == 0) { 355 | strongestClientfound = 0; 356 | return 0; // lost the strongest BSSID, returning an invalid value 357 | } 358 | } 359 | 360 | if (strongestClientfound == 0) { // on no clients connected, we change scan mode (if possible) 361 | if (modeRes & WIFI_MODE_APSTA) { 362 | if (WiFi.status() == WL_CONNECTED) { 363 | scanMode = SCANMODE_STA; 364 | } else { 365 | scanMode = SCANMODE_WIFIPROBE; // let's change scan mode 366 | } 367 | } 368 | rssi = 0; // in the meanwhile we return zero rssi 369 | } 370 | // returning the strongest signal 371 | 372 | 373 | memcpy ( BSSIDinUse, strongestClientBSSID, (sizeof(uint8_t) * 6)); // saving the current BSSID in use 374 | 375 | return rssi; 376 | } 377 | 378 | uint8_t strongestBSSID[6] = {0}; 379 | int strongestRSSI = -100; 380 | int strongestChannel = 0; 381 | int strongestAPfound = 0; // if found set to 1 382 | 383 | int bistatic_get_rssi_ScanStrongestAP() { 384 | int rssi = 0; 385 | int scanRes = 0; 386 | 387 | uint8_t * currentBSSID; 388 | int currentRSSI = 0; 389 | int currentChannel = 0; 390 | 391 | 392 | if (strongestAPfound == 0) { // don't have a strongest AP on record yet? Do a slow full scan 393 | scanRes = (int) WiFi.scanNetworks(false, false, false, 300, 0); //scanNetworks(bool async = false, bool show_hidden = false, bool passive = false, uint32_t max_ms_per_chan = 300, uint8_t channel = 0); 394 | } else { // do a single channel active scan, this should be much faster 395 | scanRes = (int) WiFi.scanNetworks(false, false, false, 200, strongestChannel); //scanNetworks(bool async = false, bool show_hidden = false, bool passive = false, uint32_t max_ms_per_chan = 300, uint8_t channel = 0); 396 | } 397 | 398 | // extracting the strongest signal (we need to mark it for subsequent scans, in order to build reliable stats) // this is executed one time when strongestAPfound == 0 399 | if (strongestAPfound == 0) { 400 | strongestRSSI = -100; 401 | for (int netItem = 0; netItem < scanRes; netItem++) { 402 | currentBSSID = WiFi.BSSID(netItem); 403 | currentRSSI = WiFi.RSSI(netItem); 404 | currentChannel = WiFi.channel(netItem); 405 | 406 | if ((currentRSSI > -100) || (currentRSSI < 0)) { 407 | if (currentRSSI > strongestRSSI) { 408 | strongestAPfound = 1; 409 | strongestRSSI = currentRSSI; 410 | strongestChannel = currentChannel; 411 | memcpy ( strongestBSSID, currentBSSID, (sizeof(uint8_t) * 6)); 412 | 413 | } 414 | } 415 | } 416 | } 417 | 418 | int bssidScanOK = 0; 419 | if (strongestAPfound == 1) { // looking for the specific BSSID, if not found then reset strongestAPfound to zero and return an invalid RSSI set to 0 420 | for (int netItem = 0; netItem < scanRes; netItem++) { // parse all the network items found, look for the known strongest BSSID 421 | currentBSSID = WiFi.BSSID(netItem); 422 | for (int bssidIndex = 0; bssidIndex < 6; bssidIndex++) { // compare with the strongest BSSID, byte by byte 423 | if (currentBSSID[bssidIndex] == strongestBSSID[bssidIndex]) { 424 | bssidScanOK = 1; 425 | } else { 426 | bssidScanOK = 0; // match failed, resetting the flag 427 | break; // no matter wich single byte failed to match, we break out of the compare loop 428 | } 429 | } 430 | if (bssidScanOK == 1) { 431 | currentRSSI = WiFi.RSSI(netItem); 432 | currentChannel = WiFi.channel(netItem); 433 | 434 | rssi = currentRSSI; // preparing the final result 435 | break; // exit the scan loop 436 | } 437 | } 438 | if (bssidScanOK == 0) { 439 | strongestAPfound = 0; 440 | strongestChannel = 0; 441 | WiFi.scanDelete(); 442 | return 0; // lost the strongest BSSID, returning an invalid value 443 | } 444 | } 445 | 446 | if (strongestAPfound == 0) { 447 | strongestChannel = 0; 448 | rssi = 0; 449 | if (modeRes & WIFI_MODE_APSTA) { 450 | scanMode = SCANMODE_SOFTAP; // let's change scan mode 451 | } 452 | } 453 | 454 | 455 | memcpy ( BSSIDinUse, strongestBSSID, (sizeof(uint8_t) * 6)); // saving the current BSSID in use 456 | 457 | // returning the strongest signal 458 | WiFi.scanDelete(); 459 | 460 | return rssi; 461 | } 462 | 463 | 464 | void serialPrintBSSID(uint8_t * localBSSID) { 465 | for (int bssidIndex = 0; bssidIndex < 6; bssidIndex++) { 466 | if (localBSSID == NULL) { 467 | Serial.print("NULL"); 468 | return; 469 | } 470 | 471 | Serial.print(localBSSID[bssidIndex], HEX); 472 | } 473 | } 474 | 475 | 476 | int motionDetector_esp() { // request the RSSI level internally, then process the signal and return the detection level in variance arbitrary units, self-detects faults and seeks for alternate solutions 477 | 478 | int RSSIlevel = 0; 479 | int res = 0; 480 | 481 | int scanRes = 0; 482 | 483 | modeRes = (int) WiFi.getMode(); 484 | 485 | if (modeRes & WIFI_MODE_NULL) { 486 | 487 | return WIFI_MODEINVALID; 488 | } 489 | if ((modeRes & WIFI_MODE_APSTA) || (modeRes & WIFI_MODE_STA)) { 490 | RSSIlevel = (int)WiFi.RSSI(); // I know, this is lame, but will make this library work with most Arduino-supported devices 491 | } 492 | // if the RSSI is zero, then we are most probably not connected to an upstream AP. 493 | 494 | if (RSSIlevel == 0) { 495 | 496 | if ((modeRes & WIFI_MODE_APSTA) || (modeRes & WIFI_MODE_AP)) { // also SoftAP available 497 | // we first check for any connected clients, then scan if zero clients have been found 498 | 499 | if (scanMode == SCANMODE_SOFTAP) { 500 | 501 | RSSIlevel = bistatic_get_rssi_SoftAP_strongestClient(); 502 | } 503 | 504 | if (scanMode == SCANMODE_WIFIPROBE) { 505 | 506 | RSSIlevel = bistatic_get_rssi_ScanStrongestAP(); 507 | } 508 | 509 | if ((RSSIlevel == 0) && (scanMode == SCANMODE_SOFTAP)){ // SoftAP scan for connected clients failed, switching scan mode 510 | 511 | scanMode = SCANMODE_WIFIPROBE; 512 | RSSIlevel = bistatic_get_rssi_ScanStrongestAP(); 513 | } 514 | 515 | if ((RSSIlevel == 0) && (scanMode == SCANMODE_WIFIPROBE)){ // WiFi probe scan for APs failed, switching scan mode 516 | 517 | scanMode = SCANMODE_SOFTAP; 518 | RSSIlevel = bistatic_get_rssi_SoftAP_strongestClient(); 519 | } 520 | 521 | 522 | if (RSSIlevel == 0) { // also no APs around to be scanned 523 | 524 | scanMode == SCANMODE_SOFTAP; // it is still worth reverting to the most efficient scan mode. 525 | 526 | } 527 | } 528 | 529 | if (modeRes & WIFI_MODE_STA) { // STA only mode 530 | 531 | scanMode = SCANMODE_WIFIPROBE; 532 | if (scanMode == SCANMODE_WIFIPROBE) { 533 | RSSIlevel = bistatic_get_rssi_ScanStrongestAP(); 534 | } 535 | if (RSSIlevel == 0) { 536 | 537 | } 538 | 539 | } 540 | 541 | 542 | if (RSSIlevel == 0) { // also no APs around to be scanned 543 | 544 | scanMode == SCANMODE_SOFTAP; // it is still worth reverting to the most efficient scan mode. 545 | 546 | return RADAR_INOPERABLE; // radar inoperable 547 | } 548 | } 549 | 550 | res = motionDetector_process(RSSIlevel); // the core operation won't change. 551 | 552 | if (enableCSVout) { 553 | Serial.println("VarianceLevel"); 554 | serialPrintBSSID(BSSIDinUse); 555 | Serial.print("_"); 556 | Serial.println(RSSIlevel); 557 | Serial.println(res); 558 | } 559 | 560 | return res; 561 | 562 | } 563 | 564 | int motionDetector_enable_serial_CSV_graph_data(int serialCSVen = 0) { 565 | 566 | if (serialCSVen >= 0) { 567 | enableCSVout = serialCSVen; 568 | } 569 | if (serialCSVen > 0) { 570 | //motionDetector_set_debug_level(0); 571 | } 572 | 573 | return serialCSVen; 574 | 575 | } 576 | 577 | int motionDetector_set_minimum_RSSI(int rssiMin) { 578 | 579 | if ((rssiMin > 0) || (rssiMin < ABSOLUTE_RSSI_LIMIT)) { 580 | rssiMin == ABSOLUTE_RSSI_LIMIT; // which results in disabling the minimum RSSI check 581 | } 582 | 583 | minimumRSSI = rssiMin; 584 | return rssiMin; 585 | } 586 | 587 | 588 | // current status: IMPLEMENTED 589 | int motionDetector_enable_alarm(int thresholdEnable) { 590 | if (thresholdEnable < 0) { 591 | thresholdEnable = 0; // which results in disabling the alarm 592 | } 593 | 594 | 595 | enableThreshold = thresholdEnable; // Lol! 596 | 597 | return enableThreshold; 598 | 599 | } 600 | 601 | 602 | // current status: IMPLEMENTED 603 | int motionDetector_set_alarm_threshold(int alarmThreshold) { 604 | if (alarmThreshold < 0) { 605 | alarmThreshold = 0; // which results in always enabling the alarm 606 | } 607 | 608 | 609 | 610 | varianceThreshold = alarmThreshold; 611 | 612 | return alarmThreshold; 613 | } 614 | -------------------------------------------------------------------------------- /data/uPlot.iife.min.js: -------------------------------------------------------------------------------- 1 | /*! https://github.com/leeoniya/uPlot (v1.6.7) */ 2 | var uPlot=function(){"use strict";function n(n,r,e,t){var l;e=e||0;for(var i=2147483647>=(t=t||r.length-1);t-e>1;)n>r[l=i?e+t>>1:m((e+t)/2)]?e=l:t=l;return n-r[e]>r[t]-n?t:e}function r(n,r,e,t){for(var l=1==t?r:e;l>=r&&e>=l;l+=t)if(null!=n[l])return l;return-1}var e=[0,0];function t(n,r,t,l){return e[0]=0>t?C(n,-t):n,e[1]=0>l?C(r,-l):r,e}function l(n,r,e,l){var i,a,o,s=10==e?k:y;return n==r&&(n/=e,r*=e),l?(i=m(s(n)),a=g(s(r)),n=(o=t(b(e,i),b(e,a),i,a))[0],r=o[1]):(i=m(s(d(n))),a=m(s(d(r))),n=Y(n,(o=t(b(e,i),b(e,a),i,a))[0]),r=W(r,o[1])),[n,r]}function i(n,r,e,t){var i=l(n,r,e,t);return 0==n&&(i[0]=0),0==r&&(i[1]=0),i}var a={pad:0,soft:null,mode:0},o={min:a,max:a};function s(n,r,e,t){return O(e)?f(n,r,e):(a.pad=e,a.soft=t?0:null,a.mode=t?3:0,f(n,r,o))}function u(n,r){return null==n?r:n}function f(n,r,e){var t=e.min,l=e.max,i=u(t.pad,0),a=u(l.pad,0),o=u(t.hard,-S),s=u(l.hard,S),f=u(t.soft,S),c=u(l.soft,-S),v=u(t.mode,0),h=u(l.mode,0),p=r-n,g=p||d(r)||1e3,_=k(g),y=b(10,m(_)),M=C(Y(n-g*(0==p?0==n?.1:1:i),y/10),6),T=f>n||1!=v&&(3!=v||M>f)&&(2!=v||f>M)?S:f,z=x(o,T>M&&n>=T?T:w(T,M)),D=C(W(r+g*(0==p?0==r?.1:1:a),y/10),6),E=r>c||1!=h&&(3!=h||c>D)&&(2!=h||D>c)?-S:c,P=w(s,D>E&&E>=r?E:x(E,D));return z==P&&0==z&&(P=100),[z,P]}var c=new Intl.NumberFormat(navigator.language).format,v=Math,h=v.PI,d=v.abs,m=v.floor,p=v.round,g=v.ceil,w=v.min,x=v.max,b=v.pow,_=v.sqrt,k=v.log10,y=v.log2,M=(n,r)=>(void 0===r&&(r=1),v.asinh(n/r)),S=1/0;function T(n,r){return p(n/r)*r}function z(n,r,e){return w(x(n,r),e)}function D(n){return"function"==typeof n?n:()=>n}var E=(n,r)=>r,P=()=>null,A=()=>!0;function W(n,r){return g(n/r)*r}function Y(n,r){return m(n/r)*r}function C(n,r){return p(n*(r=Math.pow(10,r)))/r}var H=new Map;function F(n){return((""+n).split(".")[1]||"").length}function N(n,r,e,t){for(var l=[],i=t.map(F),a=r;e>a;a++)for(var o=d(a),s=C(b(n,a),o),u=0;t.length>u;u++){var f=t[u]*s,c=(0>f||0>a?o:0)+(i[u]>a?i[u]:0),v=C(f,c);l.push(v),H.set(v,c)}return l}var I={},L=Array.isArray;function V(n){return"string"==typeof n}function O(n){var r=!1;if(null!=n){var e=n.constructor;r=null==e||e==Object}return r}function j(n){return null!=n&&"object"==typeof n}function G(n,r){var e;if(r=r||O,L(n))e=n.map((n=>G(n,r)));else if(r(n))for(var t in e={},n)e[t]=G(n[t],r);else e=n;return e}function U(n){for(var r=arguments,e=1;r.length>e;e++){var t=r[e];for(var l in t)O(n[l])?U(n[l],G(t[l])):n[l]=G(t[l])}return n}function B(n,r,e){for(var t=0,l=void 0,i=-1;r.length>t;t++){var a=r[t];if(a>i){for(l=a-1;l>=0&&null==n[l];)n[l--]=null;for(l=a+1;e>l&&null==n[l];)n[i=l++]=null}}}var R="undefined"==typeof queueMicrotask?n=>Promise.resolve().then(n):queueMicrotask,J="width",q="height",Z="top",X="bottom",K="left",Q="right",$="#000",nn="#0000",rn="mousemove",en="mousedown",tn="mouseup",ln="mouseenter",an="mouseleave",on="dblclick",sn="resize",un="scroll",fn="u-off",cn="u-label",vn=document,hn=window,dn=devicePixelRatio;function mn(n,r){if(null!=r){var e=n.classList;!e.contains(r)&&e.add(r)}}function pn(n,r){var e=n.classList;e.contains(r)&&e.remove(r)}function gn(n,r,e){n.style[r]=e+"px"}function wn(n,r,e,t){var l=vn.createElement(n);return null!=r&&mn(l,r),null!=e&&e.insertBefore(l,t),l}function xn(n,r){return wn("div",n,r)}function bn(n,r,e,t,l){n.style.transform="translate("+r+"px,"+e+"px)",0>r||0>e||r>t||e>l?mn(n,fn):pn(n,fn)}var _n={passive:!0};function kn(n,r,e){r.addEventListener(n,e,_n)}function yn(n,r,e){r.removeEventListener(n,e,_n)}var Mn=["January","February","March","April","May","June","July","August","September","October","November","December"],Sn=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];function Tn(n){return n.slice(0,3)}var zn=Sn.map(Tn),Dn=Mn.map(Tn),En={MMMM:Mn,MMM:Dn,WWWW:Sn,WWW:zn};function Pn(n){return(10>n?"0":"")+n}var An={YYYY:n=>n.getFullYear(),YY:n=>(n.getFullYear()+"").slice(2),MMMM:(n,r)=>r.MMMM[n.getMonth()],MMM:(n,r)=>r.MMM[n.getMonth()],MM:n=>Pn(n.getMonth()+1),M:n=>n.getMonth()+1,DD:n=>Pn(n.getDate()),D:n=>n.getDate(),WWWW:(n,r)=>r.WWWW[n.getDay()],WWW:(n,r)=>r.WWW[n.getDay()],HH:n=>Pn(n.getHours()),H:n=>n.getHours(),h:n=>{var r=n.getHours();return 0==r?12:r>12?r-12:r},AA:n=>12>n.getHours()?"AM":"PM",aa:n=>12>n.getHours()?"am":"pm",a:n=>12>n.getHours()?"a":"p",mm:n=>Pn(n.getMinutes()),m:n=>n.getMinutes(),ss:n=>Pn(n.getSeconds()),s:n=>n.getSeconds(),fff:n=>function(n){return(10>n?"00":100>n?"0":"")+n}(n.getMilliseconds())};function Wn(n,r){r=r||En;for(var e,t=[],l=/\{([a-z]+)\}|[^{]+/gi;e=l.exec(n);)t.push("{"==e[0][0]?An[e[1]]:e[0]);return n=>{for(var e="",l=0;t.length>l;l++)e+="string"==typeof t[l]?t[l]:t[l](n,r);return e}}var Yn=(new Intl.DateTimeFormat).resolvedOptions().timeZone,Cn=n=>n%1==0,Hn=[1,2,2.5,5],Fn=N(10,-16,0,Hn),Nn=N(10,0,16,Hn),In=Nn.filter(Cn),Ln=Fn.concat(Nn),Vn="{YYYY}",On="\n"+Vn,jn="{M}/{D}",Gn="\n"+jn,Un=Gn+"/{YY}",Bn="{aa}",Rn="{h}:{mm}"+Bn,Jn="\n"+Rn,qn=":{ss}",Zn=null;function Xn(n){var r=1e3*n,e=60*r,t=60*e,l=24*t,i=30*l,a=365*l;return[(1==n?N(10,0,3,Hn).filter(Cn):N(10,-3,0,Hn)).concat([r,5*r,10*r,15*r,30*r,e,5*e,10*e,15*e,30*e,t,2*t,3*t,4*t,6*t,8*t,12*t,l,2*l,3*l,4*l,5*l,6*l,7*l,8*l,9*l,10*l,15*l,i,2*i,3*i,4*i,6*i,a,2*a,5*a,10*a,25*a,50*a,100*a]),[[a,Vn,Zn,Zn,Zn,Zn,Zn,Zn,1],[28*l,"{MMM}",On,Zn,Zn,Zn,Zn,Zn,1],[l,jn,On,Zn,Zn,Zn,Zn,Zn,1],[t,"{h}"+Bn,Un,Zn,Gn,Zn,Zn,Zn,1],[e,Rn,Un,Zn,Gn,Zn,Zn,Zn,1],[r,qn,Un+" "+Rn,Zn,Gn+" "+Rn,Zn,Jn,Zn,1],[n,qn+".{fff}",Un+" "+Rn,Zn,Gn+" "+Rn,Zn,Jn,Zn,1]],function(r){return(o,s,u,f,c,v)=>{var h=[],d=c>=a,p=c>=i&&a>c,g=r(u),w=g*n,x=or(g.getFullYear(),d?0:g.getMonth(),p||d?1:g.getDate()),b=x*n;if(p||d)for(var _=p?c/i:0,k=d?c/a:0,y=w==b?w:or(x.getFullYear()+k,x.getMonth()+_,1)*n,M=new Date(y/n),S=M.getFullYear(),T=M.getMonth(),z=0;f>=y;z++){var D=or(S+k*z,T+_*z,1);(y=(+D+(D-r(D*n)))*n)>f||h.push(y)}else{var E=l>c?c:l,P=b+(m(u)-m(w))+W(w-b,E);h.push(P);for(var A=r(P),Y=A.getHours()+A.getMinutes()/e+A.getSeconds()/t,H=c/t,F=v/o.axes[s]._space;(P=C(P+c,1==n?0:3))<=f;)if(H>1){var N=m(C(Y+H,6))%24,I=r(P).getHours()-N;I>1&&(I=-1),Y=(Y+H)%24,.7>C(((P-=I*t)-h[h.length-1])/c,3)*F||h.push(P)}else h.push(P)}return h}}]}var Kn=Xn(1),Qn=Kn[0],$n=Kn[1],nr=Kn[2],rr=Xn(.001),er=rr[0],tr=rr[1],lr=rr[2];function ir(n,r){return n.map((n=>n.map(((e,t)=>0==t||8==t||null==e?e:r(1==t||0==n[8]?e:n[1]+e)))))}function ar(n,r){return(e,t,l,i,a)=>{var o,s,u,f,c,v,h=r.find((n=>a>=n[0]))||r[r.length-1];return t.map((r=>{var e=n(r),t=e.getFullYear(),l=e.getMonth(),i=e.getDate(),a=e.getHours(),d=e.getMinutes(),m=e.getSeconds(),p=t!=o&&h[2]||l!=s&&h[3]||i!=u&&h[4]||a!=f&&h[5]||d!=c&&h[6]||m!=v&&h[7]||h[1];return o=t,s=l,u=i,f=a,c=d,v=m,p(e)}))}}function or(n,r,e){return new Date(n,r,e)}function sr(n,r){return r(n)}function ur(n,r){return(e,t)=>r(n(t))}function fr(n,r){var e=n.series[r];return e.width?e.stroke(n,r):e.points.width?e.points.stroke(n,r):null}function cr(n,r){return n.series[r].fill(n,r)}N(2,-53,53,[1]);var vr=[0,0];function hr(n,r,e){return n=>{0==n.button&&e(n)}}function dr(n,r,e){return e}var mr={show:!0,x:!0,y:!0,lock:!1,move:function(n,r,e){return vr[0]=r,vr[1]=e,vr},points:{show:function(n,r){var e=n.cursor.points,t=xn(),l=e.stroke(n,r),i=e.fill(n,r);t.style.background=i||l;var a=e.size(n,r),o=e.width(n,r,a);o&&(t.style.border=o+"px solid "+l);var s=a/-2;return gn(t,J,a),gn(t,q,a),gn(t,"marginLeft",s),gn(t,"marginTop",s),t},size:function(n,r){return Yr(n.series[r].width,1)},width:0,stroke:function(n,r){return n.series[r].stroke(n,r)},fill:function(n,r){return n.series[r].stroke(n,r)}},bind:{mousedown:hr,mouseup:hr,click:hr,dblclick:hr,mousemove:dr,mouseleave:dr,mouseenter:dr},drag:{setScale:!0,x:!0,y:!1,dist:0,uni:null,_x:!1,_y:!1},focus:{prox:-1},left:-10,top:-10,idx:null,dataIdx:function(n,r,e){return e}},pr={show:!0,stroke:"rgba(0,0,0,0.07)",width:2,filter:E},gr=U({},pr,{size:10}),wr='12px system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',xr="bold "+wr,br={show:!0,scale:"x",stroke:$,space:50,gap:5,size:50,labelSize:30,labelFont:xr,side:2,grid:pr,ticks:gr,font:wr,rotate:0},_r={show:!0,scale:"x",auto:!1,sorted:1,min:S,max:-S,idxs:[]};function kr(n,r){return r.map((n=>null==n?"":c(n)))}function yr(n,r,e,t,l,i,a){for(var o=[],s=H.get(l)||0,u=e=a?e:C(W(e,l),s);t>=u;u=C(u+l,s))o.push(Object.is(u,-0)?0:u);return o}function Mr(n,r,e,t,l){var i=[],a=n.scales[n.axes[r].scale].log,o=m((10==a?k:y)(e));l=b(a,o),0>o&&(l=C(l,-o));var s=e;do{i.push(s),l*a>(s=C(s+l,H.get(l)))||(l=s)}while(t>=s);return i}function Sr(n,r,e,t,l){var i=n.scales[n.axes[r].scale].asinh,a=t>i?Mr(n,r,x(i,e),t,l):[i],o=0>t||e>0?[]:[0];return(-i>e?Mr(n,r,x(i,-t),-e,l):[i]).reverse().map((n=>-n)).concat(o,a)}var Tr=/./,zr=/[12357]/,Dr=/[125]/,Er=/1/;function Pr(n,r,e){var t=n.axes[e],l=t.scale,i=n.scales[l];if(3==i.distr&&2==i.log)return r;var a=n.valToPos,o=t._space,s=a(10,l),u=a(9,l)-s4==i.distr&&0==n||u.test(n)?n:null))}function Ar(n,r){return null==r?"":c(r)}var Wr={show:!0,scale:"y",stroke:$,space:30,gap:5,size:50,labelSize:30,labelFont:xr,side:3,grid:pr,ticks:gr,font:wr,rotate:0};function Yr(n,r){return C((3+2*(n||1))*r,3)}function Cr(n,r){var e=n.scales[n.series[r].scale],t=n.bands&&n.bands.some((n=>n.series[0]==r));return 3==e.distr||t?e.min:0}var Hr={scale:"y",auto:!0,sorted:0,show:!0,band:!1,spanGaps:!1,alpha:1,points:{show:function(n,r){var e=n.series[0].idxs;return(0==n.scales[n.series[0].scale].ori?n.bbox.width:n.bbox.height)/(n.series[r].points.space*dn)>=e[1]-e[0]}},values:null,min:S,max:-S,idxs:[],path:null,clip:null};function Fr(n,r,e){return e/10}var Nr={time:!0,auto:!0,distr:1,log:10,asinh:1,min:null,max:null,dir:1,ori:0},Ir=U({},Nr,{time:!1,ori:1}),Lr={};function Vr(n){var r=Lr[n];if(!r){var e=[];r={key:n,sub:function(n){e.push(n)},unsub:function(n){e=e.filter((r=>r!=n))},pub:function(n,r,t,l,i,a){for(var o=0;e.length>o;o++)e[o]!=r&&e[o].pub(n,r,t,l,i,a,o)}},null!=n&&(Lr[n]=r)}return r}function Or(n,r,e){var t=n.series[r],l=n.scales,i=n.bbox,a=n._data[0],o=n._data[r],s=l[n.series[0].scale],u=l[t.scale],f=i.left,c=i.top,v=i.width,h=i.height,d=n.valToPosH,m=n.valToPosV;return 0==s.ori?e(t,a,o,s,u,d,m,f,c,v,h,Br,Jr,Zr,Kr,$r):e(t,a,o,s,u,m,d,c,f,h,v,Rr,qr,Xr,Qr,ne)}function jr(n,r,e,t,l){return Or(n,r,((n,r,i,a,o,s,u,f,c,v,h)=>{var d,m,p=0==a.ori?Jr:qr;1==a.dir*(0==a.ori?1:-1)?(d=e,m=t):(d=t,m=e);var g=T(s(r[d],a,v,f),.5),w=T(u(i[d],o,h,c),.5),x=T(s(r[m],a,v,f),.5),b=T(u(o.max,o,h,c),.5),_=new Path2D(l);return p(_,x,b),p(_,g,b),p(_,g,w),_}))}function Gr(n,r,e,t,l,i){var a=null;if(n.length>0){a=new Path2D;for(var o=0==r?Zr:Xr,s=e,u=0;n.length>u;u++){var f=n[u];o(a,s,t,f[0]-s,t+i),s=f[1]}o(a,s,t,e+l-s,t+i)}return a}function Ur(n,r,e){if(e>r){var t=n[n.length-1];t&&t[0]==r?t[1]=e:n.push([r,e])}}function Br(n,r,e){n.moveTo(r,e)}function Rr(n,r,e){n.moveTo(e,r)}function Jr(n,r,e){n.lineTo(r,e)}function qr(n,r,e){n.lineTo(e,r)}function Zr(n,r,e,t,l){n.rect(r,e,t,l)}function Xr(n,r,e,t,l){n.rect(e,r,l,t)}function Kr(n,r,e,t,l,i){n.arc(r,e,t,l,i)}function Qr(n,r,e,t,l,i){n.arc(e,r,t,l,i)}function $r(n,r,e,t,l,i,a){n.bezierCurveTo(r,e,t,l,i,a)}function ne(n,r,e,t,l,i,a){n.bezierCurveTo(e,r,l,t,a,i)}function re(n){return(r,e,t,l,i)=>{t!=l&&(n(r,e,t),n(r,e,l),n(r,e,i))}}var ee=re(Jr),te=re(qr);function le(){return(n,e,t,l)=>Or(n,e,((i,a,o,s,u,f,c,v,h,d,m)=>{var g,b;0==s.ori?(g=Jr,b=ee):(g=qr,b=te);var _,k,y,M=s.dir*(0==s.ori?1:-1),z={stroke:new Path2D,fill:null,clip:null,band:null},D=z.stroke,E=S,P=-S,A=[],W=p(f(a[1==M?t:l],s,d,v)),Y=!1,C=r(o,t,l,1*M),H=r(o,t,l,-1*M),F=T(f(a[C],s,d,v),.5),N=T(f(a[H],s,d,v),.5);F>v&&Ur(A,v,F);for(var I=1==M?t:l;I>=t&&l>=I;I+=M){var L=p(f(a[I],s,d,v));if(L==W)null!=o[I]?(_=p(c(o[I],u,m,h)),E==S&&g(D,L,_),E=w(_,E),P=x(_,P)):Y||null!==o[I]||(Y=!0);else{var V=!1;E!=S?(b(D,W,E,P,_),k=y=W):Y&&(V=!0,Y=!1),null!=o[I]?(g(D,L,_=p(c(o[I],u,m,h))),E=P=_,L-W>1&&null===o[I-M]&&(V=!0)):(E=S,P=-S,Y||null!==o[I]||(Y=!0)),V&&Ur(A,k,L),W=L}}if(E!=S&&E!=P&&y!=W&&b(D,W,E,P,_),v+d>N&&Ur(A,N,v+d),null!=i.fill){var O=z.fill=new Path2D(D),j=p(c(i.fillTo(n,e,i.min,i.max),u,m,h));g(O,N,j),g(O,F,j)}return i.spanGaps||(z.clip=Gr(A,s.ori,v,h,d,m)),n.bands.length>0&&(z.band=jr(n,e,t,l,D)),z}))}function ie(n,r,e,t,l){var i,a,o,s,u,f,c,v,h,d,m,g,w,x,k,y,M,S,T,z,D,E,P,A,W,Y=new Path2D,C=n.length;t(Y,p(n[0]),p(r[0]));for(var H=0;C-1>H;H++){var F=0==H?0:H-1;a=r[F],s=r[H],u=n[H+1],f=r[H+1],C>H+2?(c=n[H+2],v=r[H+2]):(c=u,v=f),w=_(b((i=n[F])-(o=n[H]),2)+b(a-s,2)),x=_(b(o-u,2)+b(s-f,2)),k=_(b(u-c,2)+b(f-v,2)),z=b(k,e),E=b(k,2*e),D=b(x,e),P=b(x,2*e),(S=3*(W=b(w,e))*(W+D))>0&&(S=1/S),(T=3*z*(z+D))>0&&(T=1/T),d=(-P*a+(y=2*(A=b(w,2*e))+3*W*D+P)*s+A*f)*S,g=(E*s+(M=2*E+3*z*D+P)*f-P*v)*T,0==(h=(-P*i+y*o+A*u)*S)&&0==d&&(h=o,d=s),0==(m=(E*o+M*u-P*c)*T)&&0==g&&(m=u,g=f),l(Y,h,d,m,g,u,f)}return Y}var ae=le();function oe(n,r,e,t){return(t?[n[0],n[1]].concat(n.slice(2)):[n[0]].concat(n.slice(1))).map(((n,t)=>se(n,t,r,e)))}function se(n,r,e,t){return U({},0==r?e:t,n)}var ue=[null,null];function fe(n,r,e){return null==r?ue:[r,e]}var ce=fe;function ve(n,r,e){return null==r?ue:s(r,e,.1,!0)}function he(n,r,e,t){return null==r?ue:l(r,e,n.scales[t].log,!1)}var de=he;function me(n,r,e,t){return null==r?ue:i(r,e,n.scales[t].log,!1)}var pe=me;function ge(n){var r;return[n=n.replace(/(\d+)px/,((n,e)=>(r=p(e*dn))+"px")),r]}function we(r,e,t){var a={};function o(n,r){return((3==r.distr?k(n>0?n:r.clamp(a,n,r.min,r.max,r.key)):4==r.distr?M(n,r.asinh):n)-r._min)/(r._max-r._min)}function f(n,r,e,t){var l=o(n,r);return t+e*(-1==r.dir?1-l:l)}function c(n,r,e,t){var l=o(n,r);return t+e*(-1==r.dir?l:1-l)}function _(n,r,e,t){return 0==r.ori?f(n,r,e,t):c(n,r,e,t)}a.valToPosH=f,a.valToPosV=c;var y=!1;a.status=0;var Y=a.root=xn("uplot");null!=r.id&&(Y.id=r.id),mn(Y,r.class),r.title&&(xn("u-title",Y).textContent=r.title);var F=wn("canvas"),N=a.ctx=F.getContext("2d"),B=xn("u-wrap",Y),$=xn("u-under",B);B.appendChild(F);var _n=xn("u-over",B),Mn=u((r=G(r)).pxAlign,!0);(r.plugins||[]).forEach((n=>{n.opts&&(r=n.opts(a,r)||r)}));var Sn=r.ms||.001,Tn=a.series=oe(r.series||[],_r,Hr,!1),zn=a.axes=oe(r.axes||[],br,Wr,!0),Dn=a.scales={},En=a.bands=r.bands||[];En.forEach((n=>{n.fill=D(n.fill||null)}));var Pn=Tn[0].scale,An={axes:function(){zn.forEach(((n,r)=>{if(n.show&&n._show){var e=Dn[n.scale],t=n.side,l=t%2,i=0==l?qr:Zr,o=0==l?Ur:Jr,s=p(n.gap*dn),u=n.ticks,f=u.show?p(u.size*dn):0,c=n._found,v=c[0],d=c[1],m=n._splits,g=2==e.distr?m.map((n=>Ae[n])):m,w=2==e.distr?Ae[m[1]]-Ae[m[0]]:v,x=n._rotate*-h/180,b=p(n._pos*dn),k=b+(f+s)*(0==l&&0==t||1==l&&3==t?-1:1),y=0==l?k:0,M=1==l?k:0;N.font=n.font[0],N.fillStyle=n.stroke(a,r),N.textAlign=1==n.align?K:2==n.align?Q:x>0?K:0>x?Q:0==l?"center":3==t?Q:K,N.textBaseline=x||1==l?"middle":2==t?Z:X;var S=1.5*n.font[1],T=m.map((n=>p(_(n,e,i,o))));if(n._values.forEach(((n,r)=>{null!=n&&(0==l?M=T[r]:y=T[r],(""+n).split(/\n/gm).forEach(((n,r)=>{x?(N.save(),N.translate(M,y+r*S),N.rotate(x),N.fillText(n,0,0),N.restore()):N.fillText(n,M,y+r*S)})))})),n.label){N.save();var z=p(n._lpos*dn);1==l?(M=y=0,N.translate(z,p(Jr+Zr/2)),N.rotate((3==t?-h:h)/2)):(M=p(Ur+qr/2),y=z),N.font=n.labelFont[0],N.textAlign="center",N.textBaseline=2==t?Z:X,N.fillText(n.label,M,y),N.restore()}u.show&&Ne(T,u.filter(a,g,r,d,w),l,t,b,f,C(u.width*dn,3),u.stroke(a,r),u.dash,u.cap);var D=n.grid;D.show&&Ne(T,D.filter(a,g,r,d,w),l,0==l?2:1,0==l?Jr:Ur,0==l?Zr:qr,C(D.width*dn,3),D.stroke(a,r),D.dash,D.cap)}})),Ct("drawAxes")},series:function(){Se>0&&(Tn.forEach(((n,r)=>{if(r>0&&n.show&&null==n._paths){var t=function(n){for(var r=z(De-1,0,Se-1),e=z(Ee+1,0,Se-1);null==n[r]&&r>0;)r--;for(;null==n[e]&&Se-1>e;)e++;return[r,e]}(e[r]);n._paths=n.paths(a,r,t[0],t[1])}})),Tn.forEach(((n,r)=>{r>0&&n.show&&(n._paths&&function(n){var r=Tn[n],e=r._paths,t=e.stroke,l=e.fill,i=e.clip,o=C(r.width*dn,3),s=o%2/2,u=r._stroke=r.stroke(a,n),f=r._fill=r.fill(a,n);N.globalAlpha=r.alpha;var c=Mn&&r.pxAlign;c&&N.translate(s,s),N.save();var v=Ur,h=Jr,d=qr,m=Zr,p=o*dn/2;0==r.min&&(m+=p),0==r.max&&(h-=p,m+=p),N.beginPath(),N.rect(v,h,d,m),N.clip(),i&&N.clip(i),function(n,r,e,t,l,i,o,s){var u=!1;En.forEach(((f,c)=>{if(f.series[0]==n){var v=Tn[f.series[1]],h=(v._paths||I).band;N.save();var d=null;v.show&&h&&(d=f.fill(a,c)||i,N.clip(h)),Fe(r,e,t,l,d,o,s),N.restore(),u=!0}})),u||Fe(r,e,t,l,i,o,s)}(n,u,o,r.dash,r.cap,f,t,l),N.restore(),c&&N.translate(-s,-s),N.globalAlpha=1}(r),n.points.show(a,r,De,Ee)&&function(n){var r=Tn[n],t=r.points,l=C(t.width*dn,3),i=l%2/2,o=t.width>0,s=(t.size-t.width)/2*dn,u=C(2*s,3),f=Mn&&r.pxAlign;f&&N.translate(i,i),N.save(),N.beginPath(),N.rect(Ur-u,Jr-u,qr+2*u,Zr+2*u),N.clip(),N.globalAlpha=r.alpha;var c,v,d,m,g=new Path2D,w=Dn[r.scale];0==jn.ori?(c=qr,v=Ur,d=Zr,m=Jr):(c=Zr,v=Jr,d=qr,m=Ur);for(var x=De;Ee>=x;x++)if(null!=e[n][x]){var b=p(Fn(e[0][x],jn,c,v)),_=p(Nn(e[n][x],w,d,m));Vn(g,b+s,_),On(g,b,_,s,0,2*h)}var k=t._stroke=t.stroke(a,n),y=t._fill=t.fill(a,n);He(k,l,t.dash,t.cap,y||(o?"#fff":r._stroke)),N.fill(g),o&&N.stroke(g),N.globalAlpha=1,N.restore(),f&&N.translate(-i,-i)}(r),Ct("drawSeries",r))})))}},Yn=(r.drawOrder||["axes","series"]).map((n=>An[n]));function Cn(n){var e=Dn[n];if(null==e){var t=(r.scales||I)[n]||I;if(null!=t.from)Cn(t.from),Dn[n]=U({},Dn[t.from],t);else{(e=Dn[n]=U({},n==Pn?Nr:Ir,t)).key=n;var l=e.time,i=e.range,a=L(i);if(n!=Pn&&!a&&O(i)){var o=i;i=(n,r,e)=>null==r?ue:s(r,e,o)}e.range=D(i||(l?ce:n==Pn?3==e.distr?de:4==e.distr?pe:fe:3==e.distr?he:4==e.distr?me:ve)),e.auto=D(!a&&e.auto),e.clamp=D(e.clamp||Fr),e._min=e._max=null}}}for(var Hn in Cn("x"),Cn("y"),Tn.forEach((n=>{Cn(n.scale)})),zn.forEach((n=>{Cn(n.scale)})),r.scales)Cn(Hn);var Fn,Nn,Vn,On,jn=Dn[Pn],Gn=jn.distr;0==jn.ori?(mn(Y,"u-hz"),Fn=f,Nn=c,Vn=Br,On=Kr):(mn(Y,"u-vt"),Fn=c,Nn=f,Vn=Rr,On=Qr);var Un={};for(var Bn in Dn){var Rn=Dn[Bn];null==Rn.min&&null==Rn.max||(Un[Bn]={min:Rn.min,max:Rn.max},Rn.min=Rn.max=null)}var Jn,qn=r.tzDate||(n=>new Date(n/Sn)),Zn=r.fmtDate||Wn,Xn=1==Sn?nr(qn):lr(qn),Kn=ar(qn,ir(1==Sn?$n:tr,Zn)),rr=ur(qn,sr("{YYYY}-{MM}-{DD} {h}:{mm}{aa}",Zn)),or=U({show:!0,live:!0},r.legend),vr=or.show;or.width=D(u(or.width,2)),or.dash=D(or.dash||"solid"),or.stroke=D(or.stroke||fr),or.fill=D(or.fill||cr);var hr,dr=[],pr=!1;if(vr){Jn=wn("table","u-legend",Y);var gr=Tn[1]?Tn[1].values:null;if(pr=null!=gr){var wr=wn("tr","u-thead",Jn);for(var xr in wn("th",null,wr),hr=gr(a,1,0))wn("th",cn,wr).textContent=xr}else hr={_:0},mn(Jn,"u-inline"),or.live&&mn(Jn,"u-live")}var Tr=new Map;function zr(n,r,e){var t=Tr.get(r)||{},l=ie.bind[n](a,r,e);l&&(kn(n,r,t[n]=l),Tr.set(r,t))}var Dr=0,Er=0,Lr=0,Or=0,jr=0,Gr=0,Ur=0,Jr=0,qr=0,Zr=0;a.bbox={};var Xr=!1,$r=!1,ne=!1,re=!1,ee=!1;function te(n,r){n==a.width&&r==a.height||le(n,r),Ve(!1),ne=!0,$r=!0,re=!0,ee=!0,nt()}function le(n,r){a.width=Dr=Lr=n,a.height=Er=Or=r,jr=Gr=0,function(){var n=!1,r=!1,e=!1,t=!1;zn.forEach((l=>{if(l.show&&l._show){var i=l.side,a=i%2,o=l._size+(l.labelSize=null!=l.label?l.labelSize||30:0);o>0&&(a?(Lr-=o,3==i?(jr+=o,t=!0):e=!0):(Or-=o,0==i?(Gr+=o,n=!0):r=!0))}})),ye[0]=n,ye[1]=e,ye[2]=r,ye[3]=t,Lr-=ze[1]+ze[3],jr+=ze[3],Or-=ze[2]+ze[0],Gr+=ze[0]}(),function(){var n=jr+Lr,r=Gr+Or,e=jr,t=Gr;function l(l,i){switch(l){case 1:return(n+=i)-i;case 2:return(r+=i)-i;case 3:return(e-=i)+i;case 0:return(t-=i)+i}}zn.forEach((n=>{if(n.show&&n._show){var r=n.side;n._pos=l(r,n._size),null!=n.label&&(n._lpos=l(r,n.labelSize))}}))}();var e=a.bbox;Ur=e.left=T(jr*dn,.5),Jr=e.top=T(Gr*dn,.5),qr=e.width=T(Lr*dn,.5),Zr=e.height=T(Or*dn,.5)}a.setSize=function(n){te(n.width,n.height)};var ie=a.cursor=U({},mr,r.cursor);ie._lock=!1;var we=ie.points;we.show=D(we.show),we.size=D(we.size),we.stroke=D(we.stroke),we.width=D(we.width),we.fill=D(we.fill);var xe=a.focus=U({},r.focus||{alpha:.3},ie.focus),be=xe.prox>=0,_e=[null];function ke(n,r){var e=Dn[n.scale].time,t=n.value;if(n.value=e?V(t)?ur(qn,sr(t,Zn)):t||rr:t||Ar,n.label=n.label||(e?"Time":"Value"),r>0){n.width=null==n.width?1:n.width,n.paths=n.paths||ae||P,n.fillTo=D(n.fillTo||Cr),n.pxAlign=u(n.pxAlign,!0),n.stroke=D(n.stroke||null),n.fill=D(n.fill||null),n._stroke=n._fill=n._paths=n._focus=null;var l=Yr(n.width,1),i=n.points=U({},{size:l,width:x(1,.2*l),stroke:n.stroke,space:2*l,_stroke:null,_fill:null},n.points);i.show=D(i.show),i.fill=D(i.fill),i.stroke=D(i.stroke)}if(vr&&dr.splice(r,0,function(n,r){if(0==r&&(pr||!or.live))return null;var e=[],t=wn("tr","u-series",Jn,Jn.childNodes[r]);mn(t,n.class),n.show||mn(t,fn);var l=wn("th",null,t),i=xn("u-marker",l);if(r>0){var o=or.width(a,r);o&&(i.style.border=o+"px "+or.dash(a,r)+" "+or.stroke(a,r)),i.style.background=or.fill(a,r)}var s=xn(cn,l);for(var u in s.textContent=n.label,r>0&&(zr("click",l,(()=>{ie._lock||dt(Tn.indexOf(n),{show:!n.show},Ht.setSeries)})),be&&zr(ln,l,(()=>{ie._lock||dt(Tn.indexOf(n),mt,Ht.setSeries)}))),hr){var f=wn("td","u-value",t);f.textContent="--",e.push(f)}return e}(n,r)),ie.show){var o=function(n,r){if(r>0){var e=ie.points.show(a,r);if(e)return mn(e,"u-cursor-pt"),mn(e,n.class),bn(e,-10,-10,Lr,Or),_n.insertBefore(e,_e[r]),e}}(n,r);o&&_e.splice(r,0,o)}}a.addSeries=function(n,r){n=se(n,r=null==r?Tn.length:r,_r,Hr),Tn.splice(r,0,n),ke(Tn[r],r)},a.delSeries=function(n){Tn.splice(n,1),vr&&dr.splice(n,1)[0][0].parentNode.remove(),_e.length>1&&_e.splice(n,1)[0].remove()},Tn.forEach(ke);var ye=[!1,!1,!1,!1];function Me(n,r,e){var t=e[0],l=e[1],i=e[2],a=e[3],o=r%2,s=0;return 0==o&&(a||l)&&(s=0==r&&!t||2==r&&!i?p(br.size/3):0),1==o&&(t||i)&&(s=1==r&&!l||3==r&&!a?p(Wr.size/2):0),s}zn.forEach((function(n,r){if(n._show=n.show,n.show){var e=Dn[n.scale];null==e&&(n.scale=n.side%2?Tn[1].scale:Pn,e=Dn[n.scale]);var t=e.time;n.size=D(n.size),n.space=D(n.space),n.rotate=D(n.rotate),n.incrs=D(n.incrs||(2==e.distr?In:t?1==Sn?Qn:er:Ln)),n.splits=D(n.splits||(t&&1==e.distr?Xn:3==e.distr?Mr:4==e.distr?Sr:yr)),n.stroke=D(n.stroke),n.grid.stroke=D(n.grid.stroke),n.ticks.stroke=D(n.ticks.stroke);var l=n.values;n.values=t?L(l)?ar(qn,ir(l,Zn)):V(l)?function(n,r){var e=Wn(r);return(r,t)=>t.map((r=>e(n(r))))}(qn,l):l||Kn:l||kr,n.filter=D(n.filter||(3>e.distr?E:Pr)),n.font=ge(n.font),n.labelFont=ge(n.labelFont),n._size=n.size(a,null,r,0),n._space=n._rotate=n._incrs=n._found=n._splits=n._values=null,n._size>0&&(ye[r]=!0)}}));var Se,Te=a.padding=(r.padding||[Me,Me,Me,Me]).map((n=>D(u(n,Me)))),ze=a._padding=Te.map(((n,r)=>n(a,r,ye,0))),De=null,Ee=null,Pe=Tn[0].idxs,Ae=null,We=!1;function Ye(n,r){if((n=n||[])[0]=n[0]||[],a.data=n,e=n.slice(),Se=(Ae=e[0]).length,2==Gn&&(e[0]=Ae.map(((n,r)=>r))),a._data=e,Ve(!0),Ct("setData"),!1!==r){var t=jn;t.auto(a,We)?Ce():ht(Pn,t.min,t.max),re=ie.left>=0,ee=!0,nt()}}function Ce(){var n,r,t,a,o;We=!0,Se>0?(De=Pe[0]=0,Ee=Pe[1]=Se-1,a=e[0][De],o=e[0][Ee],2==Gn?(a=De,o=Ee):1==Se&&(3==Gn?(a=(n=l(a,a,jn.log,!1))[0],o=n[1]):4==Gn?(a=(r=i(a,a,jn.log,!1))[0],o=r[1]):jn.time?o=a+86400/Sn:(a=(t=s(a,o,.1,!0))[0],o=t[1]))):(De=Pe[0]=a=null,Ee=Pe[1]=o=null),ht(Pn,a,o)}function He(n,r,e,t,l){N.strokeStyle=n||nn,N.lineWidth=r,N.lineJoin="round",N.lineCap=t||"butt",N.setLineDash(e||[]),N.fillStyle=l||nn}function Fe(n,r,e,t,l,i,a){He(n,r,e,t,l),l&&a&&N.fill(a),n&&i&&r&&N.stroke(i)}function Ne(n,r,e,t,l,i,a,o,s,u){var f=a%2/2;Mn&&N.translate(f,f),He(o,a,s,u),N.beginPath();var c,v,h,d,m=l+(0==t||3==t?-i:i);0==e?(v=l,d=m):(c=l,h=m),n.forEach(((n,t)=>{null!=r[t]&&(0==e?c=h=n:v=d=n,N.moveTo(c,v),N.lineTo(h,d))})),N.stroke(),Mn&&N.translate(-f,-f)}function Ie(n){var r=!0;return zn.forEach(((e,t)=>{if(e.show){var l=Dn[e.scale];if(null!=l.min){e._show||(r=!1,e._show=!0,Ve(!1));var i=e.side,o=l.min,s=l.max,u=function(n,r,e,t){var l,i=zn[n];if(t>0){var o=i._space=i.space(a,n,r,e,t),s=i._incrs=i.incrs(a,n,r,e,t,o);l=i._found=function(n,r,e,t,l){for(var i=t/(r-n),a=(""+m(n)).length,o=0;e.length>o;o++){var s=e[o]*i,u=10>e[o]?H.get(e[o]):0;if(s>=l&&17>a+u)return[e[o],s]}return[0,0]}(r,e,s,t,o)}else l=[0,0];return l}(t,o,s,0==i%2?Lr:Or),f=u[0],c=u[1];if(0!=c){var v=e._splits=e.splits(a,t,o,s,f,c,2==l.distr),h=2==l.distr?v.map((n=>Ae[n])):v,d=2==l.distr?Ae[v[1]]-Ae[v[0]]:f,p=e._values=e.values(a,e.filter(a,h,t,c,d),t,c,d);e._rotate=2==i?e.rotate(a,p,t,c):0;var w=e._size;e._size=g(e.size(a,p,t,n)),null!=w&&e._size!=w&&(r=!1)}}else e._show&&(r=!1,e._show=!1,Ve(!1))}})),r}function Le(n){var r=!0;return Te.forEach(((e,t)=>{var l=e(a,t,ye,n);l!=ze[t]&&(r=!1),ze[t]=l})),r}function Ve(n){Tn.forEach(((r,e)=>{e>0&&(r._paths=null,n&&(r.min=null,r.max=null))}))}a.setData=Ye;var Oe,je,Ge,Ue,Be,Re,Je,qe,Ze,Xe,Ke,Qe,$e=!1;function nt(){$e||(R(rt),$e=!0)}function rt(){Xr&&(function(){var r=G(Dn,j);for(var t in r){var l=r[t],i=Un[t];if(null!=i&&null!=i.min)U(l,i),t==Pn&&Ve(!0);else if(t!=Pn)if(0==Se&&null==l.from){var o=l.range(a,null,null,t);l.min=o[0],l.max=o[1]}else l.min=S,l.max=-S}if(Se>0)for(var s in Tn.forEach(((t,l)=>{var i=t.scale,o=r[i],s=Un[i];if(0==l){var u=o.range(a,o.min,o.max,i);o.min=u[0],o.max=u[1],De=n(o.min,e[0]),Ee=n(o.max,e[0]),o.min>e[0][De]&&De++,e[0][Ee]>o.max&&Ee--,t.min=Ae[De],t.max=Ae[Ee]}else if(t.show&&t.auto&&o.auto(a,We)&&(null==s||null==s.min)){var f=null==t.min?3==o.distr?function(n,r,e){for(var t=S,l=-S,i=r;e>=i;i++)n[i]>0&&(t=w(t,n[i]),l=x(l,n[i]));return[t==S?1:t,l==-S?10:l]}(e[l],De,Ee):function(n,r,e,t){var l=S,i=-S;if(1==t)l=n[r],i=n[e];else if(-1==t)l=n[e],i=n[r];else for(var a=r;e>=a;a++)null!=n[a]&&(l=w(l,n[a]),i=x(i,n[a]));return[l,i]}(e[l],De,Ee,t.sorted):[t.min,t.max];o.min=w(o.min,t.min=f[0]),o.max=x(o.max,t.max=f[1])}t.idxs[0]=De,t.idxs[1]=Ee})),r){var u=r[s],f=Un[s];if(null==u.from&&(null==f||null==f.min)){var c=u.range(a,u.min==S?null:u.min,u.max==-S?null:u.max,s);u.min=c[0],u.max=c[1]}}for(var v in r){var h=r[v];if(null!=h.from){var d=r[h.from],m=h.range(a,d.min,d.max,v);h.min=m[0],h.max=m[1]}}var p={},g=!1;for(var b in r){var _=r[b],y=Dn[b];if(y.min!=_.min||y.max!=_.max){y.min=_.min,y.max=_.max;var T=y.distr;y._min=3==T?k(y.min):4==T?M(y.min,y.asinh):y.min,y._max=3==T?k(y.max):4==T?M(y.max,y.asinh):y.max,p[b]=g=!0}}if(g){for(var z in Tn.forEach((n=>{p[n.scale]&&(n._paths=null)})),p)ne=!0,Ct("setScale",z);ie.show&&(re=ie.left>=0)}for(var D in Un)Un[D]=null}(),Xr=!1),ne&&(function(){for(var n=!1,r=0;!n;){var e=Ie(++r),t=Le(r);(n=e&&t)||(le(a.width,a.height),$r=!0)}}(),ne=!1),$r&&(gn($,K,jr),gn($,Z,Gr),gn($,J,Lr),gn($,q,Or),gn(_n,K,jr),gn(_n,Z,Gr),gn(_n,J,Lr),gn(_n,q,Or),gn(B,J,Dr),gn(B,q,Er),F.width=p(Dr*dn),F.height=p(Er*dn),Mt(),Ct("setSize"),$r=!1),Dr>0&&Er>0&&(N.clearRect(0,0,F.width,F.height),Ct("drawClear"),Yn.forEach((n=>n())),Ct("draw")),ie.show&&re&&(kt(),re=!1),y||(y=!0,a.status=1,Ct("ready")),We=!1,$e=!1}function et(r,t){var l=Dn[r];if(null==l.from){if(0==Se){var i=l.range(a,t.min,t.max,r);t.min=i[0],t.max=i[1]}if(t.min>t.max){var o=t.min;t.min=t.max,t.max=o}if(Se>1&&null!=t.min&&null!=t.max&&1e-16>t.max-t.min)return;r==Pn&&2==l.distr&&Se>0&&(t.min=n(t.min,e[0]),t.max=n(t.max,e[0])),Un[r]=t,Xr=!0,nt()}}a.redraw=(n,r)=>{ne=r||!1,!1!==n?ht(Pn,jn.min,jn.max):nt()},a.setScale=et;var tt=!1,lt=ie.drag,it=lt.x,at=lt.y;ie.show&&(ie.x&&(Oe=xn("u-cursor-x",_n)),ie.y&&(je=xn("u-cursor-y",_n)),0==jn.ori?(Ge=Oe,Ue=je):(Ge=je,Ue=Oe),Ke=ie.left,Qe=ie.top);var ot,st,ut,ft=a.select=U({show:!0,over:!0,left:0,width:0,top:0,height:0},r.select),ct=ft.show?xn("u-select",ft.over?_n:$):null;function vt(n,r){if(ft.show){for(var e in n)gn(ct,e,ft[e]=n[e]);!1!==r&&Ct("setSelect")}}function ht(n,r,e){et(n,{min:r,max:e})}function dt(n,r,e){var t=Tn[n];null!=r.focus&&function(n){if(n!=ut){var r=null==n,e=1!=xe.alpha;Tn.forEach(((t,l)=>{var i=r||0==l||l==n;t._focus=r?null:i,e&&function(n,r){Tn[n].alpha=r,ie.show&&_e[n]&&(_e[n].style.opacity=r),vr&&dr[n]&&(dr[n][0].parentNode.style.opacity=r)}(l,i?1:xe.alpha)})),ut=n,e&&nt()}}(n),null!=r.show&&(t.show=r.show,function(n){var r=vr?dr[n][0].parentNode:null;Tn[n].show?r&&pn(r,fn):(r&&mn(r,fn),_e.length>1&&bn(_e[n],-10,-10,Lr,Or))}(n),ht(t.scale,null,null),nt()),Ct("setSeries",n,r),e&&Nt("setSeries",a,n,r)}a.setSelect=vt,a.setSeries=dt;var mt={focus:!0},pt={focus:!1};function gt(n,r){var e=Dn[r],t=Lr;1==e.ori&&(n=(t=Or)-n),-1==e.dir&&(n=t-n);var l=e._min,i=l+n/t*(e._max-l),a=e.distr;return 3==a?b(10,i):4==a?((n,r)=>(void 0===r&&(r=1),v.sinh(n/r)))(i,e.asinh):i}function wt(n,r){gn(ct,K,ft.left=n),gn(ct,J,ft.width=r)}function xt(n,r){gn(ct,Z,ft.top=n),gn(ct,q,ft.height=r)}vr&&be&&kn(an,Jn,(()=>{ie._lock||(dt(null,pt,Ht.setSeries),kt())})),a.valToIdx=r=>n(r,e[0]),a.posToIdx=function(r){return n(gt(r,Pn),e[0],De,Ee)},a.posToVal=gt,a.valToPos=(n,r,e)=>0==Dn[r].ori?f(n,Dn[r],e?qr:Lr,e?Ur:0):c(n,Dn[r],e?Zr:Or,e?Jr:0),a.batch=function(n){n(a),nt()},a.setCursor=n=>{Ke=n.left,Qe=n.top,kt()};var bt=0==jn.ori?wt:xt,_t=1==jn.ori?wt:xt;function kt(r,t){var l,i;Ze=Ke,Xe=Qe,l=ie.move(a,Ke,Qe),Ke=l[0],Qe=l[1],ie.show&&(Ge&&bn(Ge,p(Ke),0,Lr,Or),Ue&&bn(Ue,0,p(Qe),Lr,Or)),ot=S;var o=0==jn.ori?Lr:Or,s=1==jn.ori?Lr:Or;if(0>Ke||0==Se||De>Ee){i=null;for(var u=0;Tn.length>u;u++)if(u>0&&_e.length>1&&bn(_e[u],-10,-10,Lr,Or),vr&&or.live){if(0==u&&pr)continue;for(var f=0;dr[u].length>f;f++)dr[u][f].firstChild.nodeValue="--"}be&&dt(null,mt,Ht.setSeries)}else{var c=gt(0==jn.ori?Ke:Qe,Pn);i=n(c,e[0],De,Ee);for(var v=W(Fn(e[0][i],jn,o,0),.5),h=0;Tn.length>h;h++){var m=Tn[h],g=ie.dataIdx(a,h,i,c),x=g==i?v:W(Fn(e[0][g],jn,o,0),.5);if(h>0&&m.show){var b=e[h][g],_=null==b?-10:W(Nn(b,Dn[m.scale],s,0),.5);if(_>0){var k=d(_-Qe);k>ot||(ot=k,st=h)}var M=void 0,T=void 0;0==jn.ori?(M=x,T=_):(M=_,T=x),_e.length>1&&bn(_e[h],M,T,Lr,Or)}if(vr&&or.live){if(g==ie.idx&&!ee||0==h&&pr)continue;var z=0==h&&2==Gn?Ae:e[h],D=pr?m.values(a,h,g):{_:m.value(a,z[g],h,g)},E=0;for(var P in D)dr[h][E++].firstChild.nodeValue=D[P]}}ee=!1}if(ft.show&&tt)if(null!=t){var A=Ht.scales,Y=A[0],C=A[1],H=t.cursor.drag;it=H._x,at=H._y;var F,N,I,L,V,O=t.select,j=O.left,G=O.top,U=O.width,B=O.height,R=t.scales[Y].ori,J=t.posToVal;Y&&(0==R?(F=j,N=U):(F=G,N=B),I=Dn[Y],L=Fn(J(F,Y),I,o,0),V=Fn(J(F+N,Y),I,o,0),bt(w(L,V),d(V-L)),C||_t(0,s)),C&&(1==R?(F=j,N=U):(F=G,N=B),I=Dn[C],L=Nn(J(F,C),I,s,0),V=Nn(J(F+N,C),I,s,0),_t(w(L,V),d(V-L)),Y||bt(0,o))}else{var q=d(Ze-Be),Z=d(Xe-Re);if(1==jn.ori){var X=q;q=Z,Z=X}it=lt.x&&q>=lt.dist,at=lt.y&&Z>=lt.dist;var K,Q,$=lt.uni;null!=$?it&&at&&(at=Z>=$,(it=q>=$)||at||(Z>q?at=!0:it=!0)):lt.x&<.y&&(it||at)&&(it=at=!0),it&&(0==jn.ori?(K=Je,Q=Ke):(K=qe,Q=Qe),bt(w(K,Q),d(Q-K)),at||_t(0,s)),at&&(1==jn.ori?(K=Je,Q=Ke):(K=qe,Q=Qe),_t(w(K,Q),d(Q-K)),it||bt(0,o)),it||at||(bt(0,0),_t(0,0))}if(ie.idx=i,ie.left=Ke,ie.top=Qe,lt._x=it,lt._y=at,null!=r&&(Nt(rn,a,Ke,Qe,o,s,i),be)){var nn=Ht.setSeries,en=xe.prox;null==ut?ot>en||dt(st,mt,nn):ot>en?dt(null,mt,nn):st!=ut&&dt(st,mt,nn)}y&&Ct("setCursor")}var yt=null;function Mt(){yt=_n.getBoundingClientRect()}function St(n,r,e,t,l,i){ie._lock||(Tt(n,r,e,t,l,i,0,!1,null!=n),null!=n?kt(1):kt(null,r))}function Tt(n,r,e,t,l,i,o,s,u){var f;if(null!=n)e=n.clientX-yt.left,t=n.clientY-yt.top;else{if(0>e||0>t)return Ke=-10,void(Qe=-10);var c=Lr,v=Or,h=l,d=i,m=e,p=t;1==jn.ori&&(c=Or,v=Lr);var g=Ht.scales,w=g[0],x=g[1];if(1==r.scales[w].ori&&(h=i,d=l,m=t,p=e),e=null!=w?_(r.posToVal(m,w),Dn[w],c,0):c*(m/h),t=null!=x?_(r.posToVal(p,x),Dn[x],v,0):v*(p/d),1==jn.ori){var b=e;e=t,t=b}}u&&(e>1&&Lr-1>e||(e=T(e,Lr)),t>1&&Or-1>t||(t=T(t,Or))),s?(Be=e,Re=t,f=ie.move(a,e,t),Je=f[0],qe=f[1]):(Ke=e,Qe=t)}function zt(){vt({width:0,height:0},!1)}function Dt(n,r,e,t,l,i){tt=!0,it=at=lt._x=lt._y=!1,Tt(n,r,e,t,l,i,0,!0,!1),null!=n&&(zr(tn,vn,Et),Nt(en,a,Je,qe,Lr,Or,null))}function Et(n,r,e,t,l,i){tt=lt._x=lt._y=!1,Tt(n,r,e,t,l,i,0,!1,!0);var o=ft.left,s=ft.top,u=ft.width,f=ft.height,c=u>0||f>0;if(c&&vt(ft),lt.setScale&&c){var v=o,h=u,d=s,m=f;if(1==jn.ori&&(v=s,h=f,d=o,m=u),it&&ht(Pn,gt(v,Pn),gt(v+h,Pn)),at)for(var p in Dn){var g=Dn[p];p!=Pn&&null==g.from&&g.min!=S&&ht(p,gt(d+m,p),gt(d,p))}zt()}else ie.lock&&(ie._lock=!ie._lock,ie._lock||kt());null!=n&&(function(n,r){var e=Tr.get(r)||{};yn(n,r,e[n]),e[n]=null}(tn,vn),Nt(tn,a,Ke,Qe,Lr,Or,null))}function Pt(n){Ce(),zt(),null!=n&&Nt(on,a,Ke,Qe,Lr,Or,null)}var At,Wt={};Wt.mousedown=Dt,Wt.mousemove=St,Wt.mouseup=Et,Wt.dblclick=Pt,Wt.setSeries=(n,r,e,t)=>{dt(e,t)},ie.show&&(zr(en,_n,Dt),zr(rn,_n,St),zr(ln,_n,Mt),zr(an,_n,(function(){if(!ie._lock){var n=tt;if(tt){var r,e,t=!0,l=!0;0==jn.ori?(r=it,e=at):(r=at,e=it),r&&e&&(t=10>=Ke||Ke>=Lr-10,l=10>=Qe||Qe>=Or-10),r&&t&&(Ke=Je>Ke?0:Lr),e&&l&&(Qe=qe>Qe?0:Or),kt(1),tt=!1}Ke=-10,Qe=-10,kt(1),n&&(tt=n)}})),zr(on,_n,Pt),At=function(n){var r=null;function e(){r=null,n()}return function(){clearTimeout(r),r=setTimeout(e,100)}}(Mt),kn(sn,hn,At),kn(un,hn,At),a.syncRect=Mt);var Yt=a.hooks=r.hooks||{};function Ct(n,r,e){n in Yt&&Yt[n].forEach((n=>{n.call(null,a,r,e)}))}(r.plugins||[]).forEach((n=>{for(var r in n.hooks)Yt[r]=(Yt[r]||[]).concat(n.hooks[r])}));var Ht=U({key:null,setSeries:!1,filters:{pub:A,sub:A},scales:[Pn,null]},ie.sync),Ft=Vr(Ht.key);function Nt(n,r,e,t,l,i,a){Ht.filters.pub(n,r,e,t,l,i,a)&&Ft.pub(n,r,e,t,l,i,a)}function It(){Ct("init",r,e),Ye(e||r.data,!1),Un[Pn]?et(Pn,Un[Pn]):Ce(),te(r.width,r.height),kt(),vt(ft,!1)}return Ft.sub(a),a.pub=function(n,r,e,t,l,i,a){Ht.filters.sub(n,r,e,t,l,i,a)&&Wt[n](null,r,e,t,l,i,a)},a.destroy=function(){Ft.unsub(a),yn(sn,hn,At),yn(un,hn,At),Y.remove(),Ct("destroy")},t?t instanceof HTMLElement?(t.appendChild(Y),It()):t(a,It):It(),a}we.assign=U,we.fmtNum=c,we.rangeNum=s,we.rangeLog=l,we.rangeAsinh=i,we.orient=Or,we.join=function(n,r){for(var e=new Set,t=0;n.length>t;t++)for(var l=n[t][0],i=l.length,a=0;i>a;a++)e.add(l[a]);for(var o=[Array.from(e).sort(((n,r)=>n-r))],s=o[0].length,u=new Map,f=0;s>f;f++)u.set(o[0][f],f);for(var c=0;n.length>c;c++)for(var v=n[c],h=v[0],d=1;v.length>d;d++){for(var m=v[d],p=Array(s).fill(void 0),g=r?r[c][d]:1,w=[],x=0;m.length>x;x++){var b=m[x],_=u.get(h[x]);null==b?0!=g&&(p[_]=b,2==g&&w.push(_)):p[_]=b}B(p,w,s),o.push(p)}return o},we.fmtDate=Wn,we.tzDate=function(n,r){var e;return"UTC"==r||"Etc/UTC"==r?e=new Date(+n+6e4*n.getTimezoneOffset()):r==Yn?e=n:(e=new Date(n.toLocaleString("en-US",{timeZone:r}))).setMilliseconds(n.getMilliseconds()),e},we.sync=Vr,we.addGap=Ur,we.clipGaps=Gr;var xe=we.paths={};return xe.linear=le,xe.spline=function(){return(n,e,t,l)=>Or(n,e,((i,a,o,s,u,f,c,v,h,d,m)=>{var g,w,x;0==s.ori?(g=Br,x=Jr,w=$r):(g=Rr,x=qr,w=ne);var b=1*s.dir*(0==s.ori?1:-1);t=r(o,t,l,1),l=r(o,t,l,-1);for(var _=[],k=!1,y=p(f(a[1==b?t:l],s,d,v)),M=y,S=[],T=[],z=1==b?t:l;z>=t&&l>=z;z+=b){var D=o[z],E=f(a[z],s,d,v);null!=D?(k&&(Ur(_,M,E),k=!1),S.push(M=E),T.push(c(o[z],u,m,h))):null===D&&(Ur(_,M,E),k=!0)}var P={stroke:ie(S,T,.5,g,w),fill:null,clip:null,band:null},A=P.stroke;if(null!=i.fill){var W=P.fill=new Path2D(A),Y=i.fillTo(n,e,i.min,i.max),C=p(c(Y,u,m,h));x(W,M,C),x(W,y,C)}return i.spanGaps||(P.clip=Gr(_,s.ori,v,h,d,m)),n.bands.length>0&&(P.band=jr(n,e,t,l,A)),P}))},xe.stepped=function(n){var e=u(n.align,1),t=u(n.ascDesc,!1);return(n,l,i,a)=>Or(n,l,((o,s,u,f,c,v,h,d,m,g,w)=>{var x=0==f.ori?Jr:qr,b={stroke:new Path2D,fill:null,clip:null,band:null},_=b.stroke,k=1*f.dir*(0==f.ori?1:-1);i=r(u,i,a,1),a=r(u,i,a,-1);var y=[],M=!1,S=p(h(u[1==k?i:a],c,w,m)),T=p(v(s[1==k?i:a],f,g,d)),z=T;x(_,T,S);for(var D=1==k?i:a;D>=i&&a>=D;D+=k){var E=u[D],P=p(v(s[D],f,g,d));if(null!=E){var A=p(h(E,c,w,m));if(M){if(Ur(y,z,P),S!=A){var W=o.width*dn/2,Y=y[y.length-1];Y[0]+=t||1==e?W:-W,Y[1]-=t||-1==e?W:-W}M=!1}1==e?x(_,P,S):x(_,z,A),x(_,P,A),S=A,z=P}else null===E&&(Ur(y,z,P),M=!0)}if(null!=o.fill){var C=b.fill=new Path2D(_),H=o.fillTo(n,l,o.min,o.max),F=p(h(H,c,w,m));x(C,z,F),x(C,T,F)}return o.spanGaps||(b.clip=Gr(y,f.ori,d,m,g,w)),n.bands.length>0&&(b.band=jr(n,l,i,a,_)),b}))},xe.bars=function(n){var e=u((n=n||I).size,[.6,S]),t=n.align||0,l=1-e[0],i=u(e[1],S)*dn;return(n,e,a,o)=>Or(n,e,((s,u,f,c,v,h,d,m,g,b,_)=>{var k,y=0==c.ori?Zr:Xr,M=h(u[1],c,b,m)-h(u[0],c,b,m),S=M*l,z=d(s.fillTo(n,e,s.min,s.max),v,_,g),D=p(s.width*dn),E=p(w(i,M-S)-D),P=1==t?0:-1==t?E:E/2,A={stroke:new Path2D,fill:null,clip:null,band:null},W=n.bands.length>0;W&&(A.band=new Path2D,k=T(d(v.max,v,_,g),.5));for(var Y=A.stroke,C=A.band,H=c.dir*(0==c.ori?1:-1),F=1==H?a:o;F>=a&&o>=F;F+=H){var N=f[F];if(null==N){if(!W)continue;var I=r(f,1==H?a:o,F,-H),L=r(f,F,1==H?o:a,H),V=f[I];N=V+(F-I)/(L-I)*(f[L]-V)}var O=h(2==c.distr?F:u[F],c,b,m),j=d(N,v,_,g),G=p(O-P),U=p(x(j,z)),B=p(w(j,z)),R=U-B;null!=f[F]&&y(Y,G,B,E,R),W&&(U=B,y(C,G,B=k,E,R=U-B))}return null!=s.fill&&(A.fill=new Path2D(Y)),A}))},we}(); 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------