├── .github └── workflows │ ├── esp32.yml │ └── esp8266.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── examples ├── CloseBy │ └── CloseBy.ino ├── CloseByMQTT │ └── CloseByMQTT.ino ├── CloseBySonoff │ └── CloseBySonoff.ino ├── FindMy │ └── FindMy.ino ├── MonitorCSI │ ├── MonitorCSI.ino │ ├── MonitorCSI_processing │ │ └── MonitorCSI_processing.pde │ └── MonitorCSI_python │ │ └── MonitorCSI_python.py └── WatchDevice │ └── WatchDevice.ino ├── images ├── approx-example-closeby.gif ├── approx-example-closebymqtt.gif ├── approx-example-closebysonoff.gif ├── approx-example-findmy.gif ├── approx-example-watchdevice.gif └── approx-logo.svg ├── keywords.txt ├── library.properties └── src ├── Approximate.cpp ├── Approximate.h └── Approximate ├── ArpTable.cpp ├── ArpTable.h ├── Channel.cpp ├── Channel.h ├── Device.cpp ├── Device.h ├── Filter.cpp ├── Filter.h ├── Network.cpp ├── Network.h ├── Packet.h ├── PacketSniffer.cpp ├── PacketSniffer.h ├── eth_addr.h └── wifi_pkt.h /.github/workflows/esp32.yml: -------------------------------------------------------------------------------- 1 | name: esp32 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | name: Test compile examples for esp32 6 | runs-on: ubuntu-latest 7 | 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | 12 | - name: Checkout Arduino-List 13 | uses: actions/checkout@v2 14 | with: 15 | repository: davidchatting/Arduino-List 16 | ref: master 17 | path: CustomListLib 18 | 19 | - name: Compile all examples 20 | uses: ArminJo/arduino-test-compile@master 21 | with: 22 | arduino-board-fqbn: esp32:esp32:esp32 23 | platform-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json 24 | extra-arduino-cli-args: "--warnings default" 25 | required-libraries: ArduinoJson,StreamUtils,PubSubClient,AceButton 26 | -------------------------------------------------------------------------------- /.github/workflows/esp8266.yml: -------------------------------------------------------------------------------- 1 | name: esp8266 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | name: Test compile examples for esp8266 6 | runs-on: ubuntu-latest 7 | 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | 12 | - name: Checkout Arduino-List 13 | uses: actions/checkout@v2 14 | with: 15 | repository: davidchatting/Arduino-List 16 | ref: master 17 | path: CustomListLib 18 | 19 | - name: Compile all examples 20 | uses: ArminJo/arduino-test-compile@master 21 | with: 22 | arduino-board-fqbn: esp8266:esp8266:generic 23 | platform-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json 24 | extra-arduino-cli-args: "--warnings default" 25 | required-libraries: ArduinoJson,StreamUtils,PubSubClient,AceButton 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 David Chatting - github.com/davidchatting/Approximate 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![esp8266](https://github.com/davidchatting/Approximate/workflows/esp8266/badge.svg) 2 | ![esp32](https://github.com/davidchatting/Approximate/workflows/esp32/badge.svg) 3 | 4 | # The Approximate Library 5 | The Approximate library is a WiFi [Arduino](http://www.arduino.cc/download) Library for building proximate interactions between your Internet of Things and the [ESP8266](https://en.wikipedia.org/wiki/ESP8266) or [ESP32](https://en.wikipedia.org/wiki/ESP32), perhaps a switch that operates the nearest lamp or a song that plays when you (and your phone) come home. 6 | 7 | ![CloseBySonoff example](./images/approx-example-closebysonoff.gif) 8 | 9 | Technically this library makes it easy to use WiFi signal strength ([RSSI](https://en.wikipedia.org/wiki/Received_signal_strength_indication)) to estimate the physical distance to a device on your home network, then obtain its [MAC address](https://en.wikipedia.org/wiki/MAC_address) and optionally its [IP address](https://en.wikipedia.org/wiki/IPv4). The network activity of these devices can also be observed. 10 | 11 | ## Installation 12 | 13 | The latest stable release of Approximate is available in the Arduino IDE Library Manager - search for "Approximate". Click install. 14 | 15 | Alternatively, Approximate can be installed manually. First locate and open the `libraries` directory used by the Arduino IDE, then clone this repository (https://github.com/davidchatting/Approximate) into that folder - this will create a new subfolder called `Approximate`. 16 | 17 | Approximate requires that either the Arduino core for the ESP8266 or ESP32 is installed - follow these instructions: 18 | 19 | * ESP8266 - https://github.com/esp8266/Arduino#installing-with-boards-manager 20 | * ESP32 - https://github.com/espressif/arduino-esp32/blob/master/docs/arduino-ide/boards_manager.md 21 | 22 | In addition, the following libraries are also required: 23 | 24 | * ListLib - https://github.com/luisllamasbinaburo/Arduino-List (install via the Arduino IDE Library Manager - searching for "ListLib") 25 | 26 | ## Limitations 27 | Approximate works with 2.4GHz WiFi networks, but not 5GHz networks - neither ESP8266 or ESP32 support this technology. This means that devices that are connected to a 5GHz WiFi network will be invisible to this library. Approximate will not work as expected where [MAC address randomisation](https://support.apple.com/en-gb/guide/security/secb9cb3140c/web) is enabled - the default iOS setting. 28 | 29 | ## Examples 30 | This section describes in some technical detail each of the examples available from the `Examples` menu once Approximate is correctly installed. 31 | 32 | Every Approximate sketch has this essential structure: 33 | 34 | ``` 35 | #include 36 | Approximate approx; 37 | 38 | void setup() { 39 | if (approx.init("MyHomeWiFi", "password")) { 40 | approx.begin(); 41 | } 42 | } 43 | 44 | void loop() { 45 | approx.loop(); 46 | } 47 | ``` 48 | 49 | Approximate defines two types of interaction with devices: when in proximity (using a Proximate Device Handler) or simply when active (using an Active Device Handler). The examples here demonstrate combinations of these. 50 | 51 | ### When We're Close... using a Proximate Device Handler 52 | 53 | ![CloseBy example](./images/approx-example-closeby.gif) 54 | 55 | The [CloseBy example](examples/CloseBy) indicates when a WiFi device is in proximity and prints out its [MAC addresses](https://en.wikipedia.org/wiki/MAC_address). 56 | 57 | ``` 58 | #include 59 | Approximate approx; 60 | 61 | const int LED_PIN = 2; 62 | 63 | void setup() { 64 | Serial.begin(9600); 65 | pinMode(LED_PIN, OUTPUT); 66 | 67 | if (approx.init("MyHomeWiFi", "password")) { 68 | approx.setProximateDeviceHandler(onProximateDevice, APPROXIMATE_PERSONAL_RSSI); 69 | approx.begin(); 70 | } 71 | } 72 | 73 | void loop() { 74 | approx.loop(); 75 | } 76 | 77 | void onProximateDevice(Device *device, Approximate::DeviceEvent event) { 78 | switch(event) { 79 | case Approximate::ARRIVE: 80 | digitalWrite(LED_PIN, HIGH); 81 | Serial.println("ARRIVE\t" + device->getMacAddressAsString()); 82 | break; 83 | case Approximate::DEPART: 84 | digitalWrite(LED_PIN, LOW); 85 | Serial.println("DEPART\t" + device->getMacAddressAsString()); 86 | break; 87 | } 88 | } 89 | ``` 90 | 91 | This example uses a Proximate Device Handler. The `onProximateDevice()` callback function receives both a pointer to a `Device` and a `DeviceEvent` for each new observation - in this example the events `ARRIVE` and `DEPART` cause the device's [MAC address](https://en.wikipedia.org/wiki/MAC_address) to be printed out and the state to be indicated via the LED. MAC addresses are the primary way in which the Approximate library identifies network devices. 92 | 93 | There are four event types that a `DeviceHandler` will encounter: 94 | 95 | * `Approximate::ARRIVE` once when the device first arrives in proximity (only for Proximate Device Handlers) 96 | * `Approximate::DEPART` once when the device departs and is no longer seen in proximity (only for Proximate Device Handlers) 97 | * `Approximate::SEND` every time the device sends (uploads) data 98 | * `Approximate::RECEIVE` every time the device receives (downloads) data (rarely for Proximate Device Handlers, unless the router is also in proximity) 99 | 100 | The Proximate Device Handler is set by `setProximateDeviceHandler()`, which takes a `DeviceHandler` callback function parameter (here `onProximateDevice`) and a value for the `rssiThreshold` parameter that describes range considered to be in proximity (here `APPROXIMATE_PERSONAL_RSSI`). [RSSI](https://en.wikipedia.org/wiki/Received_signal_strength_indication) is a measure of WiFi signal strength used to estimate proximity. It is measured in [dBm](https://en.wikipedia.org/wiki/DBm) and at close proximity (where the reception is good) its value will approach zero, as the signal degrades over distance and through objects and walls, the value will fall. For instance, an RSSI of -50 would represent a relatively strong signal. The library predefines four values of `rssiThreshold` for use, that borrow from the language of [proxemics](https://en.wikipedia.org/wiki/Proxemics): 101 | 102 | * `APPROXIMATE_INTIMATE_RSSI` -20 dBm (10 centimetres) 103 | * `APPROXIMATE_PERSONAL_RSSI` -40 dBm (1 metre) 104 | * `APPROXIMATE_SOCIAL_RSSI` -60 dBm (4 metres) 105 | * `APPROXIMATE_PUBLIC_RSSI` -80 dBm (8 metres) 106 | 107 | These values are extremely approximate and represent the highest values that might be achieved at these ranges. `rssiThreshold` can be defined numerically and if it is not set `setProximateDeviceHandler()` defaults to a value of `APPROXIMATE_PERSONAL_RSSI`. The full definition for `setProximateDeviceHandler()` is: 108 | 109 | ``` 110 | void setProximateDeviceHandler(DeviceHandler deviceHandler, int rssiThreshold = APPROXIMATE_PERSONAL_RSSI, int lastSeenTimeoutMs = 60000); 111 | ``` 112 | 113 | The parameter `lastSeenTimeoutMs` defines how quickly (in milliseconds) a device will be said to `DEPART` if it is unseen. While the `ARRIVE` event is triggered only once for a device, further observations will cause `SEND` and (sometimes) `RECEIVE` events; when these events stop and after a wait of `lastSeenTimeoutMs`, a `DEPART` event will then be generated. A suitable value will depend on the dynamics of the application and devices' use of the network. One minute (60,000 ms) is the default value - that is used in this example. 114 | 115 | ### Find My... using an Active Device Handler 116 | ![FindMy example](./images/approx-example-findmy.gif) 117 | 118 | The [FindMy example](examples/FindMy) demonstrates how a device can be found on your WiFi network using its signal strength (as measured by [RSSI](https://en.wikipedia.org/wiki/Received_signal_strength_indication)) - the LED flashing increases as the distance decreases. 119 | 120 | ``` 121 | #include 122 | Approximate approx; 123 | 124 | const int LED_PIN = 2; 125 | bool ledState = LOW; 126 | long ledToggleAtMs = 0; 127 | int ledToggleIntervalMs = 0; 128 | 129 | void setup() { 130 | Serial.begin(9600); 131 | pinMode(LED_PIN, OUTPUT); 132 | 133 | if (approx.init("MyHomeWiFi", "password")) { 134 | approx.setActiveDeviceFilter("XX:XX:XX:XX:XX:XX"); 135 | approx.setActiveDeviceHandler(onActiveDevice); 136 | approx.begin(); 137 | } 138 | } 139 | 140 | void loop() { 141 | approx.loop(); 142 | 143 | digitalWrite(LED_PIN, ledState); 144 | 145 | if(ledToggleIntervalMs > 0 && millis() > ledToggleAtMs) { 146 | ledState = !ledState; 147 | ledToggleAtMs = millis() + ledToggleIntervalMs; 148 | } 149 | } 150 | 151 | void onActiveDevice(Device *device, Approximate::DeviceEvent event) { 152 | if(event == Approximate::SEND) { 153 | ledToggleIntervalMs = map(device->getRSSI(), -100, 0, 1000, 0); 154 | } 155 | } 156 | ``` 157 | 158 | This example uses an Active Device Handler. The `onActiveDevice()` callback function receives both a pointer to a `Device` and a `DeviceEvent` for each event. This example measures the RSSI of messages sent by the device (`event == Approximate::SEND`) to estimate its distance and displays this as a flashing LED, that speeds up as the distance decreases. 159 | 160 | The Active Device Handler is set by `Approximate::setActiveDeviceHandler()`, taking a `DeviceHandler` callback function parameter (here `onActiveDevice`), then generating `SEND` and `RECEIVE` events for active devices. The full definition for `setActiveDeviceHandler()` is: 161 | 162 | ``` 163 | void setActiveDeviceHandler(DeviceHandler activeDeviceHandler, bool inclusive = true); 164 | ``` 165 | 166 | Unlike the Proximate Device Handler the Active Device Handler is not filtered by signal strength, but instead can be filtered by [MAC address](https://en.wikipedia.org/wiki/MAC_address) or by device manufacturer with an [OUI code](https://en.wikipedia.org/wiki/Organizationally_unique_identifier). To observe a specific device, as in this example, `setActiveDeviceFilter()` takes a MAC address formatted as an String (XX:XX:XX:XX:XX:XX). Similarly, the OUI code of a specific manufacturer can be used as a filter and these can be found [here](http://standards-oui.ieee.org/oui.txt). Alterantively, rather than a single filter, a list of filters can be maintained using the `addActiveDeviceFilter()` variant. `setActiveDeviceHandler()` takes an optional parameter `inclusive` that defines the behaviour if no filters are set, whether any device should initially be included (`true`) or excluded (`false`), by default it is inclusive. Once a filter is added this initial inclusive or exclusive behaviour is overwritten. 167 | 168 | ``` 169 | void setActiveDeviceFilter(String macAddress); 170 | void setActiveDeviceFilter(int oui); 171 | 172 | void addActiveDeviceFilter(String macAddress); 173 | void addActiveDeviceFilter(int oui); 174 | ``` 175 | 176 | ### Watch Device - using a Proximate Device Handler and an Active Device Handler 177 | 178 | ![WatchDevice example](./images/approx-example-watchdevice.gif) 179 | 180 | The [WatchDevice example](examples/WatchDevice) creates a temporary pair with a proximate device and then flashes the LED to show the amount of data (number of bytes) that device is receiving. 181 | 182 | ``` 183 | #include 184 | Approximate approx; 185 | 186 | const int LED_PIN = 2; 187 | long ledOnUntilMs = 0; 188 | 189 | void setup() { 190 | Serial.begin(9600); 191 | pinMode(LED_PIN, OUTPUT); 192 | 193 | if (approx.init("MyHomeWiFi", "password")) { 194 | approx.setProximateDeviceHandler(onProximateDevice, APPROXIMATE_INTIMATE_RSSI, /*lastSeenTimeoutMs*/ 1000); 195 | approx.setActiveDeviceHandler(onActiveDevice, /*inclusive*/ false); 196 | approx.begin(); 197 | } 198 | } 199 | 200 | void loop() { 201 | approx.loop(); 202 | digitalWrite(LED_PIN, millis() < ledOnUntilMs); 203 | } 204 | 205 | void onProximateDevice(Device *device, Approximate::DeviceEvent event) { 206 | switch (event) { 207 | case Approximate::ARRIVE: 208 | Serial.println("Watching: " + device -> getMacAddressAsString()); 209 | approx.setActiveDeviceFilter(device); 210 | break; 211 | case Approximate::DEPART: 212 | break; 213 | } 214 | } 215 | 216 | void onActiveDevice(Device *device, Approximate::DeviceEvent event) { 217 | if(event == Approximate::RECEIVE) { 218 | ledOnUntilMs = millis() + (device -> getPayloadSizeBytes()/10); 219 | } 220 | } 221 | ``` 222 | 223 | This example uses both a Proximate Device Handler (`onProximateDevice()`) and an Active Device Handler (`onActiveDevice()`) acting in combination - both receives both a pointer to a `Device` and a `Approximate::DeviceEvent`. The Active Device Handler is initially set to exclude all devices and so `onActiveDevice()` will report no activity until a device is brought within proximity and a device filter is added via `onProximateDevice()`. Once the pair is made, activity will be monitored even when they are no longer in proximity. A new pair is established when a new device is then in proximity. 224 | 225 | ### Close By MQTT - make a connection to the cloud 226 | 227 | ![CloseByMQTT example](./images/approx-example-closebymqtt.gif) 228 | 229 | The [CloseByMQTT example](examples/CloseByMQTT) makes a connection to the cloud when a device is in proximity, sending a message to an [MQTT](https://en.wikipedia.org/wiki/MQTT) server. It requires the [Arduino Client for MQTT](https://github.com/knolleary/pubsubclient) library - search for "pubsubclient" in the Arduino IDE Library Manager. 230 | 231 | ``` 232 | #include 233 | #include 234 | 235 | Approximate approx; 236 | 237 | WiFiClient wifiClient; 238 | PubSubClient mqttClient(wifiClient); 239 | 240 | const int LED_PIN = 2; 241 | 242 | void setup() { 243 | Serial.begin(9600); 244 | pinMode(LED_PIN, OUTPUT); 245 | 246 | if (approx.init("MyHomeWiFi", "password")) { 247 | approx.setProximateDeviceHandler(onProximateDevice); 248 | approx.begin([]() { 249 | mqttClient.setServer("192.168.XXX.XXX", 1883); 250 | }); 251 | } 252 | } 253 | 254 | void loop() { 255 | approx.loop(); 256 | mqttClient.loop(); 257 | } 258 | 259 | void onProximateDevice(Device *device, Approximate::DeviceEvent event) { 260 | if(event == Approximate::ARRIVE || event == Approximate::DEPART) { 261 | digitalWrite(LED_PIN, event == Approximate::ARRIVE); 262 | 263 | String json = "{\"" + device->getMacAddressAsString() + "\":\"" + Approximate::toString(event) + "\"}"; 264 | Serial.println(json); 265 | 266 | approx.onceWifiStatus(WL_CONNECTED, [](String payload) { 267 | mqttClient.connect(WiFi.macAddress().c_str()); 268 | mqttClient.publish("closeby", payload.c_str(), false); //false = don't retain message 269 | 270 | #if defined(ESP8266) 271 | delay(20); 272 | approx.disconnectWiFi(); 273 | #endif 274 | }, json); 275 | approx.connectWiFi(); 276 | } 277 | } 278 | ``` 279 | 280 | This example is an extension to the CloseBy example and retains the same structure. However, its use of the network for MQTT messages requires that the WiFi status be managed. In `setup()`, when the `Approximate::begin()` function is called, if the connection can be successfully established `WiFi.status()` will achieve a state of `WL_CONNECTED` at which point network calls may be made. However, this status change will take some time and happens asynchronously. To manage this, `Approximate::begin()` takes an optional lambda function called once the connection is established and used here to set the MQTT server details. The ESP8266 must then break this connection to monitor devices and then reconnect to make network calls, unlike the ESP32 which can maintain the connection and monitor devices; the Approximate library provides a mechanism that manages both cases - namely `Approximate::onceWifiStatus()`. Like `Approximate::begin()` takes a lambda function, but it also takes a status on which this behaviour will be triggered. The function will be called at most once, if the WiFi status is immediately available or once this transition is next made. 281 | 282 | In its simplest form `Approximate::onceWifiStatus()` is used as shown below - the subsequent call to `approx.connectWiFi()` is made to establish the trigger WiFi status of `WL_CONNECTED` - if it is not already available. 283 | 284 | ``` 285 | approx.onceWifiStatus(WL_CONNECTED, []() { 286 | Serial.println("The WiFi is connected!"); 287 | }); 288 | approx.connectWiFi(); 289 | ``` 290 | 291 | The CloseByMQTT example demonstrates how `Approximate::onceWifiStatus()` can pass a parameter - here `onProximateDevice()` defines a json `String` that contains the details of the MQTT message - a `bool` parameter is also supported. Note that for an ESP8266 the WiFi must then be disconnected once the MQTT message is sent `Approximate::disconnectWiFi()`, to allow monitoring to resume. 292 | 293 | ### Close By Sonoff - interacting with devices 294 | 295 | ![CloseBySonoff example](./images/approx-example-closebysonoff.gif) 296 | 297 | The [CloseBySonoff example](examples/CloseBySonoff) demonstrates an interaction with a proximate device - namely the [Sonoff BASICR3](https://sonoff.tech/product/wifi-diy-smart-switches/basicr3) smart switch. In [DIY Mode](http://developers.sonoff.tech/sonoff-diy-mode-api-protocol.html) these devices offer a simple RESTful API, enabling the state of the socket to be set via an HTTP POST and json formatted payload. This example requires the [AceButton](https://github.com/bxparks/AceButton) library - available via the Arduino IDE Library Manager. 298 | 299 | ``` 300 | #include 301 | Approximate approx; 302 | 303 | #include 304 | 305 | #include 306 | using namespace ace_button; 307 | 308 | const int LED_PIN = 2; 309 | const int BUTTON_PIN = 0; 310 | AceButton button(BUTTON_PIN); 311 | 312 | Device *closeBySonoff = NULL; 313 | 314 | void setup() { 315 | Serial.begin(9600); 316 | 317 | pinMode(LED_PIN, OUTPUT); 318 | digitalWrite(LED_PIN, false); 319 | 320 | ButtonConfig* buttonConfig = button.getButtonConfig(); 321 | buttonConfig->setEventHandler(onButtonEvent); 322 | 323 | if (approx.init("MyHomeWiFi", "password", true)) { 324 | approx.setProximateDeviceHandler(onProximateDevice, APPROXIMATE_SOCIAL_RSSI, 10000); 325 | approx.begin(); 326 | } 327 | } 328 | 329 | void loop() { 330 | approx.loop(); 331 | button.check(); 332 | } 333 | 334 | void onProximateDevice(Device *device, Approximate::DeviceEvent event) { 335 | switch (device->getOUI()) { 336 | //D8F15B Sonoff (Expressif Inc) - see: http://standards-oui.ieee.org/oui.txt 337 | case 0xD8F15B: 338 | onCloseBySonoff(device, event); 339 | break; 340 | } 341 | } 342 | 343 | void onCloseBySonoff(Device *device, Approximate::DeviceEvent event) { 344 | switch (event) { 345 | case Approximate::ARRIVE: 346 | closeBySonoff = device; 347 | digitalWrite(LED_PIN, HIGH); 348 | break; 349 | case Approximate::DEPART: 350 | if(*device == *closeBySonoff) { 351 | closeBySonoff = NULL; 352 | digitalWrite(LED_PIN, LOW); 353 | } 354 | break; 355 | } 356 | } 357 | 358 | void onButtonEvent(AceButton* button, uint8_t eventType, uint8_t buttonState) { 359 | if(closeBySonoff) { 360 | switch (eventType) { 361 | case AceButton::kEventPressed: 362 | switchCloseBySonoff(true); 363 | break; 364 | case AceButton::kEventReleased: 365 | switchCloseBySonoff(false); 366 | break; 367 | } 368 | } 369 | } 370 | 371 | void switchCloseBySonoff(bool switchState) { 372 | if(closeBySonoff) { 373 | approx.onceWifiStatus(WL_CONNECTED, [](bool switchState) { 374 | if(closeBySonoff) { 375 | HTTPClient http; 376 | String url = "http://" + closeBySonoff->getIPAddressAsString() + ":8081/zeroconf/switch"; 377 | http.begin(url); 378 | http.addHeader("Content-Type", "application/json"); 379 | 380 | String switchValue = switchState?"on":"off"; 381 | String httpRequestData = "{\"deviceid\": \"\",\"data\": {\"switch\": \"" + switchValue + "\"}}"; 382 | 383 | int httpResponseCode = http.POST(httpRequestData); 384 | Serial.printf("%s\t%s\t%i\n",url.c_str(), httpRequestData.c_str(), httpResponseCode); 385 | http.end(); 386 | } 387 | 388 | #if defined(ESP8266) 389 | delay(20); 390 | approx.disconnectWiFi(); 391 | #endif 392 | }, switchState); 393 | approx.connectWiFi(); 394 | } 395 | } 396 | ``` 397 | 398 | This is a further extension to the CloseBy example and again retains the same structure. It uses a simple Proximate Device Handler (`onProximateDevice()`) and attempts to determine the type of the proximate device by its [OUI code](https://en.wikipedia.org/wiki/Organizationally_unique_identifier). Those identifying as `0xD8F15B` are manufactured by Expressif Inc, used by Sonoff (see http://standards-oui.ieee.org/oui.txt) - `onCloseBySonoff()` is then called. If the button is pressed and released `switchCloseBySonoff()` will be called to first turn on and then off a proximate Sonoff socket. The LED is illuminated to show that a device is present. 399 | 400 | Significantly this example requires that not only a proximate device's MAC address be known, but also its local [IP address - IPv4](https://en.wikipedia.org/wiki/IPv4) be determined. In default operation IP addresses are not available, but can be simply enabled by setting an optional parameter on `Approximate::init()` to `true`. This will initiate an [ARP scan](https://en.wikipedia.org/wiki/Address_Resolution_Protocol) of the local network when `Approximate::begin()` is called. However, this will cause an additional delay of 76 seconds on an ESP8266 and 12 seconds on an ESP32 before the main program will operate. The ESP32 will periodically automatically refresh its ARP table, but the ESP8266 will not - meaning that an ESP8266 will be unable to determine the IP address of new devices appearing on the network. 401 | 402 | ## In Use 403 | 404 | Projects that use the Approximate library include: 405 | * [Three WiFi Meters](https://github.com/davidchatting/ThreeWiFiMeters) 406 | * [Proximate Sonoff switch](https://www.youtube.com/watch?v=cXh0T1CWtyg) 407 | 408 | ## Credits 409 | 410 | The Approximate library has learnt much from the work of others, including: 411 | * [ESP8266 WifiSniffer](https://github.com/kalanda/esp8266-sniffer) 412 | * [ESP32 WiFi MAC Scanner/Sniffer](https://www.hackster.io/p99will/esp32-wifi-mac-scanner-sniffer-promiscuous-4c12f4) 413 | * [ArduinoPcap](https://github.com/spacehuhn/ArduinoPcap) 414 | * [Nexmon Channel State Information Extractor](https://github.com/seemoo-lab/nexmon_csi) 415 | * [ESP32 CSI Tool](https://github.com/StevenMHernandez/ESP32-CSI-Tool) 416 | * [ESP32 gather CSI](https://github.com/jonathanmuller/ESP32-gather-channel-state-information-CSI-) 417 | * [ESP32 CSI Phase Chart](https://github.com/diegonunesbr/ESP32-CSI-Phase-Chart) 418 | 419 | ## Author 420 | 421 | The Approximate library was created by David Chatting ([@davidchatting](https://twitter.com/davidchatting)) as part of the [A Network of One's Own](http://davidchatting.com/nooo/) project. Collaboration welcome - please contribute by raising issues and making pull requests via GitHub. This code is licensed under the [MIT License](LICENSE.txt). -------------------------------------------------------------------------------- /examples/CloseBy/CloseBy.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Close By example for the Approximate Library 3 | - 4 | Find the MAC address of close by devices - and trigger ARRIVE & DEPART events 5 | - 6 | David Chatting - github.com/davidchatting/Approximate 7 | MIT License - Copyright (c) October 2020 8 | 9 | Example documented here > https://github.com/davidchatting/Approximate/tree/master#when-were-close-using-a-proximate-device-handler 10 | */ 11 | 12 | #include 13 | Approximate approx; 14 | 15 | //Define for your board, not all have built-in LED and/or button: 16 | #if defined(ESP8266) 17 | const int LED_PIN = 14; 18 | #elif defined(ESP32) 19 | const int LED_PIN = 2; 20 | #endif 21 | 22 | void setup() { 23 | Serial.begin(9600); 24 | pinMode(LED_PIN, OUTPUT); 25 | 26 | if (approx.init("MyHomeWiFi", "password")) { 27 | approx.setProximateDeviceHandler(onProximateDevice, APPROXIMATE_PERSONAL_RSSI); 28 | approx.begin(); 29 | } 30 | } 31 | 32 | void loop() { 33 | approx.loop(); 34 | } 35 | 36 | void onProximateDevice(Device *device, Approximate::DeviceEvent event) { 37 | switch(event) { 38 | case Approximate::ARRIVE: 39 | digitalWrite(LED_PIN, HIGH); 40 | Serial.println("ARRIVE\t" + device->getMacAddressAsString()); 41 | break; 42 | case Approximate::DEPART: 43 | digitalWrite(LED_PIN, LOW); 44 | Serial.println("DEPART\t" + device->getMacAddressAsString()); 45 | break; 46 | } 47 | } -------------------------------------------------------------------------------- /examples/CloseByMQTT/CloseByMQTT.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Close By example with MQTT for the Approximate Library 3 | - 4 | Find the MAC address of close by devices then send an MQTT report 5 | - 6 | David Chatting - github.com/davidchatting/Approximate 7 | MIT License - Copyright (c) October 2020 8 | 9 | Example documented here > https://github.com/davidchatting/Approximate/tree/master#close-by-mqtt---make-a-connection-to-the-cloud 10 | */ 11 | 12 | #include 13 | #include //https://github.com/knolleary/pubsubclient 14 | 15 | Approximate approx; 16 | 17 | WiFiClient wifiClient; 18 | PubSubClient mqttClient(wifiClient); 19 | 20 | //Define for your board, not all have built-in LED and/or button: 21 | #if defined(ESP8266) 22 | const int LED_PIN = 14; 23 | #elif defined(ESP32) 24 | const int LED_PIN = 2; 25 | #endif 26 | 27 | void setup() { 28 | Serial.begin(9600); 29 | pinMode(LED_PIN, OUTPUT); 30 | 31 | if (approx.init("MyHomeWiFi", "password")) { 32 | approx.setProximateDeviceHandler(onProximateDevice); 33 | approx.begin([]() { 34 | mqttClient.setServer("192.168.XXX.XXX", 1883); 35 | }); 36 | } 37 | } 38 | 39 | void loop() { 40 | approx.loop(); 41 | mqttClient.loop(); 42 | } 43 | 44 | void onProximateDevice(Device *device, Approximate::DeviceEvent event) { 45 | if(event == Approximate::ARRIVE || event == Approximate::DEPART) { 46 | digitalWrite(LED_PIN, event == Approximate::ARRIVE); 47 | 48 | String json = "{\"" + device->getMacAddressAsString() + "\":\"" + Approximate::toString(event) + "\"}"; 49 | Serial.println(json); 50 | 51 | approx.onceWifiStatus(WL_CONNECTED, [](String payload) { 52 | mqttClient.connect(WiFi.macAddress().c_str()); 53 | mqttClient.publish("closeby", payload.c_str(), false); //false = don't retain message 54 | 55 | #if defined(ESP8266) 56 | delay(20); 57 | approx.disconnectWiFi(); 58 | #endif 59 | }, json); 60 | approx.connectWiFi(); 61 | } 62 | } -------------------------------------------------------------------------------- /examples/CloseBySonoff/CloseBySonoff.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Example of operating close by Sonoff switches (running in DIY mode) with the Approximate Library 3 | - 4 | Find the IP address of close by devices then send a POST to them 5 | - 6 | David Chatting - github.com/davidchatting/Approximate 7 | MIT License - Copyright (c) October 2020 8 | 9 | Example documented here > https://github.com/davidchatting/Approximate/tree/master#close-by-sonoff---interacting-with-devices 10 | */ 11 | 12 | #include 13 | Approximate approx; 14 | 15 | #if defined(ESP8266) 16 | #include 17 | #elif defined(ESP32) 18 | #include 19 | #endif 20 | 21 | #include 22 | using namespace ace_button; 23 | 24 | //Define for your board, not all have built-in LED and/or button: 25 | #if defined(ESP8266) 26 | const int LED_PIN = 14; 27 | const int BUTTON_PIN = 15; 28 | AceButton button(BUTTON_PIN, LOW); 29 | #elif defined(ESP32) 30 | const int LED_PIN = 2; 31 | const int BUTTON_PIN = 0; 32 | AceButton button(BUTTON_PIN); 33 | #endif 34 | 35 | Device *closeBySonoff = NULL; 36 | 37 | void setup() { 38 | Serial.begin(9600); 39 | 40 | pinMode(LED_PIN, OUTPUT); 41 | digitalWrite(LED_PIN, false); 42 | 43 | ButtonConfig* buttonConfig = button.getButtonConfig(); 44 | buttonConfig->setEventHandler(onButtonEvent); 45 | 46 | if (approx.init("MyHomeWiFi", "password", true)) { 47 | approx.setProximateDeviceHandler(onProximateDevice, APPROXIMATE_SOCIAL_RSSI, 10000); 48 | approx.begin(); 49 | } 50 | } 51 | 52 | void loop() { 53 | approx.loop(); 54 | button.check(); 55 | } 56 | 57 | void onProximateDevice(Device *device, Approximate::DeviceEvent event) { 58 | switch (device->getOUI()) { 59 | //D8F15B Sonoff (Expressif Inc) - see: http://standards-oui.ieee.org/oui.txt 60 | case 0xD8F15B: 61 | onCloseBySonoff(device, event); 62 | break; 63 | default: 64 | Serial.printf("Unknown OUI:\t0x%06X\n", device->getOUI()); 65 | break; 66 | } 67 | } 68 | 69 | void onCloseBySonoff(Device *device, Approximate::DeviceEvent event) { 70 | switch (event) { 71 | case Approximate::ARRIVE: 72 | closeBySonoff = device; 73 | digitalWrite(LED_PIN, HIGH); 74 | Serial.printf("SONOFF\t%s\t(%s)\tARRIVE\n", device -> getMacAddressAsString().c_str(), device -> getIPAddressAsString().c_str()); 75 | break; 76 | case Approximate::DEPART: 77 | if(*device == *closeBySonoff) { 78 | closeBySonoff = NULL; 79 | digitalWrite(LED_PIN, LOW); 80 | Serial.printf("SONOFF\t%s\tDEPART\n", device -> getMacAddressAsString().c_str()); 81 | } 82 | break; 83 | } 84 | } 85 | 86 | void onButtonEvent(AceButton* button, uint8_t eventType, uint8_t buttonState) { 87 | if(closeBySonoff) { 88 | switch (eventType) { 89 | case AceButton::kEventPressed: 90 | Serial.println("AceButton::kEventPressed"); 91 | switchCloseBySonoff(true); 92 | break; 93 | case AceButton::kEventReleased: 94 | Serial.println("AceButton::kEventReleased"); 95 | switchCloseBySonoff(false); 96 | break; 97 | } 98 | } 99 | } 100 | 101 | void switchCloseBySonoff(bool switchState) { 102 | if(closeBySonoff) { 103 | approx.onceWifiStatus(WL_CONNECTED, [](bool switchState) { 104 | if(closeBySonoff) { 105 | WiFiClient client; 106 | HTTPClient http; 107 | String url = "http://" + closeBySonoff->getIPAddressAsString() + ":8081/zeroconf/switch"; 108 | http.begin(client, url); 109 | http.addHeader("Content-Type", "application/json"); 110 | 111 | String switchValue = switchState?"on":"off"; 112 | String httpRequestData = "{\"deviceid\": \"\",\"data\": {\"switch\": \"" + switchValue + "\"}}"; 113 | 114 | int httpResponseCode = http.POST(httpRequestData); 115 | Serial.printf("%s\t%s\t%i\n",url.c_str(), httpRequestData.c_str(), httpResponseCode); 116 | http.end(); 117 | } 118 | 119 | #if defined(ESP8266) 120 | delay(20); 121 | approx.disconnectWiFi(); 122 | #endif 123 | }, switchState); 124 | approx.connectWiFi(); 125 | } 126 | } -------------------------------------------------------------------------------- /examples/FindMy/FindMy.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Find My example for the Approximate Library 3 | - 4 | Report the signal strenth of a device (specificed by its MAC address) 5 | - 6 | David Chatting - github.com/davidchatting/Approximate 7 | MIT License - Copyright (c) October 2020 8 | 9 | Example documented here > https://github.com/davidchatting/Approximate/tree/master#find-my--using-an-active-device-handler 10 | */ 11 | 12 | #include 13 | Approximate approx; 14 | 15 | //Define for your board, not all have built-in LED: 16 | #if defined(ESP8266) 17 | const int LED_PIN = 14; 18 | #elif defined(ESP32) 19 | const int LED_PIN = 2; 20 | #endif 21 | bool ledState = LOW; 22 | long ledToggleAtMs = 0; 23 | int ledToggleIntervalMs = 0; 24 | 25 | void setup() { 26 | Serial.begin(9600); 27 | pinMode(LED_PIN, OUTPUT); 28 | 29 | if (approx.init("MyHomeWiFi", "password")) { 30 | approx.setActiveDeviceFilter("XX:XX:XX:XX:XX:XX"); 31 | approx.setActiveDeviceHandler(onActiveDevice); 32 | approx.begin(); 33 | } 34 | } 35 | 36 | void loop() { 37 | approx.loop(); 38 | 39 | digitalWrite(LED_PIN, ledState); 40 | 41 | if(ledToggleIntervalMs > 0 && millis() > ledToggleAtMs) { 42 | ledState = !ledState; 43 | ledToggleAtMs = millis() + ledToggleIntervalMs; 44 | } 45 | } 46 | 47 | void onActiveDevice(Device *device, Approximate::DeviceEvent event) { 48 | if(event == Approximate::SEND) { 49 | ledToggleIntervalMs = map(device->getRSSI(), -100, 0, 1000, 0); 50 | } 51 | } -------------------------------------------------------------------------------- /examples/MonitorCSI/MonitorCSI.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Monitor CSI example for the Approximate Library 3 | - 4 | David Chatting - github.com/davidchatting/Approximate 5 | MIT License - Copyright (c) February 2021 6 | */ 7 | 8 | #include 9 | Approximate approx; 10 | 11 | void setup() { 12 | Serial.begin(9600); 13 | 14 | if (approx.init("MyHomeWiFi", "password", false, true)) { 15 | approx.setChannelStateHandler(onChannelStateEvent); 16 | approx.begin(); 17 | } 18 | } 19 | 20 | void loop() { 21 | approx.loop(); 22 | } 23 | 24 | void onChannelStateEvent(Channel *channel) { 25 | int8_t a, bi; 26 | for(int n = -26; n <= 26; ++n) { 27 | if(n!=0) { 28 | channel -> getSubCarrier(n, a, bi); 29 | Serial.printf("%i,%i\t", a, bi); 30 | } 31 | } 32 | Serial.printf("\n"); 33 | } -------------------------------------------------------------------------------- /examples/MonitorCSI/MonitorCSI_processing/MonitorCSI_processing.pde: -------------------------------------------------------------------------------- 1 | /* 2 | Monitor CSI example for the Approximate Library (Processing client) 3 | - 4 | David Chatting - github.com/davidchatting/Approximate 5 | MIT License - Copyright (c) February 2021 6 | */ 7 | 8 | import processing.serial.*; 9 | 10 | Serial esp32; 11 | 12 | public class CSIPoint { 13 | public int subCarrier; 14 | public PVector v; 15 | } 16 | 17 | CSIPoint sample[] = new CSIPoint[52]; 18 | 19 | void setup() { 20 | size(1024, 480); 21 | esp32 = new Serial(this, "/dev/tty.SLAB_USBtoUART", 9600); 22 | } 23 | 24 | void draw() { 25 | String line; 26 | if (esp32.available() > 0) { 27 | line = esp32.readStringUntil('\n'); 28 | if (line != null) { 29 | String[] csiPacket = split(line.trim(), '\t'); 30 | 31 | if(csiPacket.length == 52) { 32 | for(int n = -26; n < 26; ++n) { 33 | CSIPoint p = new CSIPoint(); 34 | int a = int(split(csiPacket[n+26], ',')[0]); //real 35 | int bi = int(split(csiPacket[n+26], ',')[1]); //imaginary 36 | p.v = new PVector(a, bi); 37 | p.subCarrier = n; 38 | 39 | sample[n+26] = p; 40 | } 41 | 42 | background(0); 43 | noFill(); 44 | 45 | translate(0, height/2); 46 | 47 | stroke(50, 50, 50); 48 | line(0,0,width,0); 49 | 50 | int w = width/53; 51 | stroke(255, 0, 0); 52 | beginShape(); 53 | for(int n = 0; n < 52; ++n) { 54 | curveVertex(w*n, map(sample[n].v.mag(), 0, 50, 0, -200)); 55 | } 56 | endShape(); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/MonitorCSI/MonitorCSI_python/MonitorCSI_python.py: -------------------------------------------------------------------------------- 1 | # Monitor CSI example for the Approximate Library (Python client) 2 | # - 3 | # David Chatting - github.com/davidchatting/Approximate 4 | # MIT License - Copyright (c) February 2021 5 | # 6 | # Adapted from: http://www.mikeburdis.com/wp/notes/plotting-serial-port-data-using-python-and-matplotlib/ 7 | 8 | import matplotlib.pyplot as plt 9 | import matplotlib.animation as animation 10 | from matplotlib import style 11 | import numpy as np 12 | import random 13 | from serial import Serial 14 | 15 | #initialize serial port 16 | ser = Serial() 17 | ser.port = '/dev/tty.SLAB_USBtoUART' #ESP32 serial port 18 | ser.baudrate = 9600 19 | ser.timeout = 10 #specify timeout when using readline() 20 | ser.open() 21 | if ser.is_open==True: 22 | # Create figure for plotting 23 | fig = plt.figure() 24 | ax = fig.add_subplot(1, 1, 1) 25 | xs = [] 26 | ys = [] 27 | 28 | # This function is called periodically from FuncAnimation 29 | def animate(i, xs, ys): 30 | line=ser.readline().strip() 31 | line_as_list = line.split(b'\t') 32 | 33 | if len(line_as_list)==52: 34 | for n, csipacket in enumerate(line_as_list): 35 | v = np.array(csipacket.split(b',')) #real,imaginary 36 | xs.append(n-26) #subcarrier 37 | ys.append(np.linalg.norm(v)) #magnitude 38 | 39 | # Limit x and y lists to 52 items 40 | xs = xs[-52:] 41 | ys = ys[-52:] 42 | 43 | # Draw x and y lists 44 | ax.clear() 45 | ax.plot(xs, ys) 46 | 47 | # Set up plot to call animate() function periodically 48 | ani = animation.FuncAnimation(fig, animate, fargs=(xs, ys), interval=50) 49 | plt.show() -------------------------------------------------------------------------------- /examples/WatchDevice/WatchDevice.ino: -------------------------------------------------------------------------------- 1 | /* 2 | When Active example for the Approximate Library 3 | - 4 | Report active devices on the network, optionally filtered by MAC address or OUI 5 | - 6 | David Chatting - github.com/davidchatting/Approximate 7 | MIT License - Copyright (c) October 2020 8 | 9 | Example documented here > https://github.com/davidchatting/Approximate/tree/master#watch-device---using-a-proximate-device-handler-and-an-active-device-handler 10 | */ 11 | 12 | #include 13 | Approximate approx; 14 | 15 | //Define for your board, not all have built-in LED: 16 | #if defined(ESP8266) 17 | const int LED_PIN = 14; 18 | #elif defined(ESP32) 19 | const int LED_PIN = 2; 20 | #endif 21 | 22 | long ledOnUntilMs = 0; 23 | 24 | void setup() { 25 | Serial.begin(9600); 26 | pinMode(LED_PIN, OUTPUT); 27 | 28 | if (approx.init("MyHomeWiFi", "password")) { 29 | approx.setProximateDeviceHandler(onProximateDevice, APPROXIMATE_PERSONAL_RSSI, /*lastSeenTimeoutMs*/ 1000); 30 | approx.setActiveDeviceHandler(onActiveDevice, /*inclusive*/ false); 31 | approx.begin(); 32 | } 33 | } 34 | 35 | void loop() { 36 | approx.loop(); 37 | digitalWrite(LED_PIN, millis() < ledOnUntilMs); 38 | } 39 | 40 | void onProximateDevice(Device *device, Approximate::DeviceEvent event) { 41 | switch (event) { 42 | case Approximate::ARRIVE: 43 | Serial.println("Watching: " + device -> getMacAddressAsString()); 44 | approx.setActiveDeviceFilter(device); 45 | break; 46 | case Approximate::DEPART: 47 | break; 48 | } 49 | } 50 | 51 | void onActiveDevice(Device *device, Approximate::DeviceEvent event) { 52 | if(event == Approximate::RECEIVE) { 53 | ledOnUntilMs = millis() + (device -> getPayloadSizeBytes()/10); 54 | } 55 | } -------------------------------------------------------------------------------- /images/approx-example-closeby.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidchatting/Approximate/ebd5e61c5e85ca87efd659a158019eaaab0dd74b/images/approx-example-closeby.gif -------------------------------------------------------------------------------- /images/approx-example-closebymqtt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidchatting/Approximate/ebd5e61c5e85ca87efd659a158019eaaab0dd74b/images/approx-example-closebymqtt.gif -------------------------------------------------------------------------------- /images/approx-example-closebysonoff.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidchatting/Approximate/ebd5e61c5e85ca87efd659a158019eaaab0dd74b/images/approx-example-closebysonoff.gif -------------------------------------------------------------------------------- /images/approx-example-findmy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidchatting/Approximate/ebd5e61c5e85ca87efd659a158019eaaab0dd74b/images/approx-example-findmy.gif -------------------------------------------------------------------------------- /images/approx-example-watchdevice.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidchatting/Approximate/ebd5e61c5e85ca87efd659a158019eaaab0dd74b/images/approx-example-watchdevice.gif -------------------------------------------------------------------------------- /images/approx-logo.svg: -------------------------------------------------------------------------------- 1 | approximate-logo -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map for Approximate library 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | Approximate KEYWORD1 10 | ArpTable KEYWORD1 11 | Device KEYWORD1 12 | DeviceEvent KEYWORD1 13 | DeviceHandler KEYWORD1 14 | Filter KEYWORD1 15 | Packet KEYWORD1 16 | PacketSniffer KEYWORD1 17 | PacketType KEYWORD1 18 | 19 | ####################################### 20 | # Methods and Functions (KEYWORD2) 21 | ####################################### 22 | 23 | # public methods from Approximate.h 24 | toString KEYWORD2 25 | 26 | init KEYWORD2 27 | begin KEYWORD2 28 | end KEYWORD2 29 | loop KEYWORD2 30 | isRunning KEYWORD2 31 | addActiveDeviceFilter KEYWORD2 32 | setActiveDeviceFilter KEYWORD2 33 | removeActiveDeviceFilter KEYWORD2 34 | setLocalBSSID KEYWORD2 35 | setActiveDeviceHandler KEYWORD2 36 | setProximateDeviceHandler KEYWORD2 37 | setProximateRSSIThreshold KEYWORD2 38 | setProximateLastSeenTimeoutMs 39 | connectWiFi KEYWORD2 40 | disconnectWiFi KEYWORD2 41 | onceWifiStatus KEYWORD2 42 | 43 | MacAddr_to_eth_addr KEYWORD2 44 | uint8_t_to_eth_addr KEYWORD2 45 | oui_to_eth_addr KEYWORD2 46 | String_to_eth_addr KEYWORD2 47 | eth_addr_to_String KEYWORD2 48 | eth_addr_to_c_str KEYWORD2 49 | wifi_csi_info_to_Channel KEYWORD2 50 | Packet_to_Device KEYWORD2 51 | 52 | # methods from Device.h 53 | init KEYWORD2 54 | update KEYWORD2 55 | 56 | getMacAddress KEYWORD2 57 | getMacAddressAsString KEYWORD2 58 | getMacAddressAs_c_str KEYWORD2 59 | 60 | getBssidAsString KEYWORD2 61 | getBssidAs_c_str KEYWORD2 62 | 63 | getIPAddress KEYWORD2 64 | getIPAddressAsString KEYWORD2 65 | getIPAddressAs_c_str KEYWORD2 66 | setIPAddress KEYWORD2 67 | hasIPAddress KEYWORD2 68 | 69 | setRSSI KEYWORD2 70 | getRSSI KEYWORD2 71 | 72 | setLastSeenAtMs KEYWORD2 73 | getLastSeenAtMs KEYWORD2 74 | 75 | matches KEYWORD2 76 | getOUI KEYWORD2 77 | getChannel KEYWORD2 78 | 79 | isUploading KEYWORD2 80 | isDownloading KEYWORD2 81 | getUploadSizeBytes KEYWORD2 82 | getDownloadSizeBytes KEYWORD2 83 | getPayloadSizeBytes KEYWORD2 84 | 85 | isUniversal KEYWORD2 86 | isLocal KEYWORD2 87 | isIndividual KEYWORD2 88 | isGroup KEYWORD2 89 | 90 | ####################################### 91 | # Instances (KEYWORD2) 92 | ####################################### 93 | 94 | ####################################### 95 | # Constants (LITERAL1) 96 | ####################################### 97 | 98 | # public constants from Approximate.h 99 | APPROXIMATE_INTIMATE_RSSI LITERAL1 100 | APPROXIMATE_PERSONAL_RSSI LITERAL1 101 | APPROXIMATE_SOCIAL_RSSI LITERAL1 102 | APPROXIMATE_PUBLIC_RSSI LITERAL1 103 | 104 | # PacketType: 105 | PKT_MGMT LITERAL1 106 | PKT_CTRL LITERAL1 107 | PKT_DATA LITERAL1 108 | PKT_MISC LITERAL1 109 | 110 | # DeviceEvent: 111 | ARRIVE LITERAL1 112 | DEPART LITERAL1 113 | SEND LITERAL1 114 | RECEIVE LITERAL1 115 | INACTIVE LITERAL1 116 | 117 | # public constants from Device.h 118 | APPROXIMATE_UNKNOWN_RSSI LITERAL1 -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Approximate 2 | version=1.4 3 | author=David Chatting 4 | maintainer=David Chatting 5 | sentence=The Approximate Library is a WiFi Arduino library for building proximate interactions between your Internet of Things and the ESP8266 or ESP32. 6 | paragraph=The Approximate Library is a WiFi Arduino library for building proximate interactions between your Internet of Things and the ESP8266 or ESP32. Technically it makes it easy to use WiFi signal strength (RSSI) to estimate the physical distance to a device on your home network, then obtain its MAC address and optionally its IP address. The network activity of these devices can also be observed. 7 | category=Communication 8 | url=https://github.com/davidchatting/Approximate 9 | architectures=esp32,esp8266 10 | depends=ListLib 11 | -------------------------------------------------------------------------------- /src/Approximate.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Approximate.cpp 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) October 2020 7 | */ 8 | 9 | #include "Approximate.h" 10 | 11 | bool Approximate::running = false; 12 | bool Approximate::onlyIndividualDevices = true; 13 | 14 | PacketSniffer *Approximate::packetSniffer = PacketSniffer::getInstance(); 15 | ArpTable *Approximate::arpTable = NULL; 16 | 17 | Approximate::DeviceHandler Approximate::activeDeviceHandler = NULL; 18 | Approximate::DeviceHandler Approximate::proximateDeviceHandler = NULL; 19 | Approximate::ChannelStateHandler Approximate::channelStateHandler = NULL; 20 | 21 | eth_addr Approximate::ownMacAddress = {{0,0,0,0,0,0}}; 22 | 23 | int Approximate::proximateRSSIThreshold = APPROXIMATE_PERSONAL_RSSI; 24 | eth_addr Approximate::localBSSID = {{0,0,0,0,0,0}}; 25 | List Approximate::activeDeviceFilterList; 26 | 27 | List Approximate::proximateDeviceList; 28 | int Approximate::proximateLastSeenTimeoutMs = 60000; 29 | 30 | Approximate::Approximate() { 31 | uint8_t ma[6]; 32 | WiFi.macAddress(ma); 33 | uint8_t_to_eth_addr(ma, ownMacAddress); 34 | } 35 | 36 | bool Approximate::init(String ssid, String password, bool ipAddressResolution, bool csiEnabled, bool onlyIndividualDevices) { 37 | bool success = false; 38 | 39 | if(ssid.length() > 0) { 40 | int n = WiFi.scanNetworks(); 41 | for (int i = 0; i < n && !success; ++i) { 42 | if(WiFi.SSID(i) == ssid) { 43 | if(WiFi.encryptionType(i) == 0x7 || password.length() > 0) { 44 | //Network is either open or a password is supplied 45 | strcpy(this->ssid, ssid.c_str()); 46 | strcpy(this->password, password.c_str()); 47 | 48 | if(initBlind(WiFi.channel(i), WiFi.BSSID(i), ipAddressResolution, csiEnabled, onlyIndividualDevices)) { 49 | success = true; 50 | } 51 | } 52 | } 53 | } 54 | } 55 | else { 56 | success = initBlind(ipAddressResolution, csiEnabled, onlyIndividualDevices); 57 | } 58 | 59 | return(success); 60 | } 61 | 62 | bool Approximate::initBlind(int channel, uint8_t *bssid, bool ipAddressResolution, bool csiEnabled, bool onlyIndividualDevices) { 63 | bool success = true; 64 | 65 | WiFi.disconnect(); 66 | WiFi.persistent(false); 67 | WiFi.mode(WIFI_STA); 68 | delay(100); 69 | 70 | packetSniffer -> init(channel); 71 | packetSniffer -> setPacketEventHandler(parsePacket); 72 | if(csiEnabled) packetSniffer -> setChannelEventHandler(parseChannelStateInformation); 73 | this -> onlyIndividualDevices = onlyIndividualDevices; 74 | 75 | eth_addr networkBSSID; 76 | uint8_t_to_eth_addr(bssid, networkBSSID); 77 | setLocalBSSID(networkBSSID); 78 | 79 | String networkBSSIDAsString; 80 | eth_addr_to_String(networkBSSID, networkBSSIDAsString); 81 | Serial.printf("\n-\nRouter: %s\t\tChannel: %i\n-\n", networkBSSIDAsString.c_str(), channel); 82 | 83 | if(ipAddressResolution) arpTable = ArpTable::getInstance(); 84 | 85 | return(success); 86 | } 87 | 88 | bool Approximate::initBlind(bool ipAddressResolution, bool csiEnabled, bool onlyIndividualDevices) { 89 | bool success = false; 90 | 91 | if(WiFi.status() == WL_CONNECTED) { 92 | strcpy(this->ssid, WiFi.SSID().c_str()); 93 | strcpy(this->password, WiFi.psk().c_str()); 94 | 95 | if(initBlind(WiFi.channel(), WiFi.BSSID(), ipAddressResolution, csiEnabled, onlyIndividualDevices)) { 96 | success = true; 97 | } 98 | } 99 | 100 | return(success); 101 | } 102 | 103 | void Approximate::onceWifiStatus(wl_status_t status, voidFnPtr callBackFnPtr) { 104 | if(status != WL_IDLE_STATUS) { 105 | if(WiFi.status() == status) { 106 | callBackFnPtr(); 107 | triggerWifiStatus = WL_IDLE_STATUS; 108 | } 109 | else { 110 | this -> triggerWifiStatus = status; 111 | this -> onceWifiStatusFnPtr = callBackFnPtr; 112 | } 113 | } 114 | } 115 | 116 | void Approximate::onceWifiStatus(wl_status_t status, voidFnPtrWithStringPayload callBackFnPtr, String payload) { 117 | if(status != WL_IDLE_STATUS) { 118 | if(WiFi.status() == status) { 119 | callBackFnPtr(payload); 120 | triggerWifiStatus = WL_IDLE_STATUS; 121 | } 122 | else { 123 | this -> triggerWifiStatus = status; 124 | this -> onceWifiStatusWithStringPayloadFnPtr = callBackFnPtr; 125 | this -> onceWifiStatusStringPayload = payload; 126 | } 127 | } 128 | } 129 | 130 | void Approximate::onceWifiStatus(wl_status_t status, voidFnPtrWithBoolPayload callBackFnPtr, bool payload) { 131 | if(status != WL_IDLE_STATUS) { 132 | if(WiFi.status() == status) { 133 | callBackFnPtr(payload); 134 | triggerWifiStatus = WL_IDLE_STATUS; 135 | } 136 | else { 137 | this -> triggerWifiStatus = status; 138 | this -> onceWifiStatusWithBoolPayloadFnPtr = callBackFnPtr; 139 | this -> onceWifiStatusBoolPayload = payload; 140 | } 141 | } 142 | } 143 | 144 | void Approximate::onceWifiStatus(wl_status_t status, voidFnPtrWithFnPtrPayload callBackFnPtr, voidFnPtr payload) { 145 | if(status != WL_IDLE_STATUS) { 146 | if(WiFi.status() == status) { 147 | callBackFnPtr(payload); 148 | triggerWifiStatus = WL_IDLE_STATUS; 149 | } 150 | else { 151 | this -> triggerWifiStatus = status; 152 | this -> onceWifiStatusWithFnPtrPayloadFnPtr = callBackFnPtr; 153 | this -> onceWifiStatusFnPtrPayload = payload; 154 | } 155 | } 156 | } 157 | 158 | void Approximate::begin(voidFnPtr thenFnPtr) { 159 | Serial.println("Approximate::begin"); 160 | 161 | onceWifiStatus(WL_CONNECTED, [](voidFnPtr thenFnPtr) { 162 | if(thenFnPtr) thenFnPtr(); 163 | 164 | if(arpTable) { 165 | arpTable -> scan(); //blocking 166 | arpTable -> begin(); 167 | } 168 | 169 | #if defined(ESP8266) 170 | WiFi.disconnect(); 171 | #endif 172 | 173 | //start the packetSniffer after the scan is complete: 174 | if(packetSniffer) packetSniffer -> begin(); 175 | 176 | running = true; 177 | }, thenFnPtr); 178 | connectWiFi(); 179 | Serial.println("Approximate::begin DONE"); 180 | } 181 | 182 | void Approximate::end() { 183 | if (packetSniffer) packetSniffer -> end(); 184 | if (arpTable) arpTable -> end(); 185 | 186 | running = false; 187 | } 188 | 189 | void Approximate::loop() { 190 | if(running) { 191 | if (packetSniffer) { 192 | packetSniffer -> loop(); 193 | } 194 | 195 | if (arpTable) arpTable -> loop(); 196 | 197 | updateProximateDeviceList(); 198 | } 199 | 200 | if(currentWifiStatus != WiFi.status()) { 201 | printWiFiStatus(); 202 | wl_status_t lastWifiStatus = currentWifiStatus; 203 | currentWifiStatus = WiFi.status(); 204 | onWifiStatusChange(lastWifiStatus, currentWifiStatus); 205 | } 206 | } 207 | 208 | bool Approximate::isRunning() { 209 | return(running); 210 | } 211 | 212 | void Approximate::onWifiStatusChange(wl_status_t oldStatus, wl_status_t newStatus) { 213 | if(newStatus != WL_IDLE_STATUS && newStatus == triggerWifiStatus) { 214 | if(onceWifiStatusFnPtr != NULL ) { 215 | onceWifiStatusFnPtr(); 216 | } 217 | else if(onceWifiStatusWithStringPayloadFnPtr != NULL) { 218 | onceWifiStatusWithStringPayloadFnPtr(onceWifiStatusStringPayload); 219 | } 220 | else if(onceWifiStatusWithBoolPayloadFnPtr != NULL) { 221 | onceWifiStatusWithBoolPayloadFnPtr(onceWifiStatusBoolPayload); 222 | } 223 | else if(onceWifiStatusWithFnPtrPayloadFnPtr != NULL) { 224 | onceWifiStatusWithFnPtrPayloadFnPtr(onceWifiStatusFnPtrPayload); 225 | } 226 | 227 | onceWifiStatusFnPtr = NULL; 228 | onceWifiStatusWithStringPayloadFnPtr = NULL; 229 | onceWifiStatusWithFnPtrPayloadFnPtr = NULL; 230 | triggerWifiStatus = WL_IDLE_STATUS; 231 | } 232 | } 233 | 234 | wl_status_t Approximate::connectWiFi() { 235 | return(connectWiFi(this -> ssid, this -> password)); 236 | } 237 | 238 | wl_status_t Approximate::connectWiFi(String ssid, String password) { 239 | return(connectWiFi(ssid.c_str(), password.c_str())); 240 | } 241 | 242 | wl_status_t Approximate::connectWiFi(char *ssid, char *password) { 243 | Serial.printf("Approximate::connectWiFi %s %s\n", ssid, password); 244 | 245 | if(WiFi.status() != WL_CONNECTED) { 246 | if(strlen(ssid) > 0) { 247 | #if defined(ESP8266) 248 | if (packetSniffer) packetSniffer -> end(); 249 | WiFi.begin(ssid, password); 250 | 251 | #elif defined(ESP32) 252 | //WiFi.begin() for the ESP32 (1.0.4) > https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/src/WiFiSTA.cpp - doesn't call esp_wifi_init() or esp_wifi_start() - which are needed later for esp_wifi_set_csi() 253 | tcpip_adapter_init(); 254 | esp_event_loop_init(NULL, NULL); 255 | 256 | if(!WiFi.enableSTA(true)) { 257 | log_e("STA enable failed!"); 258 | return WL_CONNECT_FAILED; 259 | } 260 | 261 | if(!ssid || *ssid == 0x00 || strlen(ssid) > 31) { 262 | log_e("SSID too long or missing!"); 263 | return WL_CONNECT_FAILED; 264 | } 265 | 266 | if(password && strlen(password) > 64) { 267 | log_e("password too long!"); 268 | return WL_CONNECT_FAILED; 269 | 270 | } 271 | 272 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 273 | esp_wifi_init(&cfg); 274 | 275 | wifi_config_t conf; 276 | memset(&conf, 0, sizeof(wifi_config_t)); 277 | strcpy(reinterpret_cast(conf.sta.ssid), ssid); 278 | 279 | if(password) { 280 | if (strlen(password) == 64){ // it's not a password, is the PSK 281 | memcpy(reinterpret_cast(conf.sta.password), password, 64); 282 | } else { 283 | strcpy(reinterpret_cast(conf.sta.password), password); 284 | } 285 | } 286 | 287 | if(esp_wifi_disconnect()){ 288 | log_e("disconnect failed!"); 289 | return WL_CONNECT_FAILED; 290 | } 291 | esp_wifi_set_config(WIFI_IF_STA, &conf); 292 | 293 | if(tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA) == ESP_ERR_TCPIP_ADAPTER_DHCPC_START_FAILED){ 294 | log_e("dhcp client start failed!"); 295 | return WL_CONNECT_FAILED; 296 | } 297 | 298 | esp_wifi_start(); 299 | 300 | if(esp_wifi_connect()) { 301 | log_e("connect failed!"); 302 | return WL_CONNECT_FAILED; 303 | } 304 | 305 | #endif 306 | } 307 | } 308 | 309 | return(WiFi.status()); 310 | } 311 | 312 | void Approximate::disconnectWiFi() { 313 | WiFi.disconnect(); 314 | 315 | #if defined(ESP8266) 316 | if (running && packetSniffer) packetSniffer -> begin(); 317 | #endif 318 | } 319 | 320 | void Approximate::printWiFiStatus() { 321 | switch(WiFi.status()) { 322 | case WL_CONNECTED: Serial.println("WL_CONNECTED"); break; 323 | case WL_NO_SHIELD: Serial.println("WL_NO_SHIELD"); break; 324 | case WL_IDLE_STATUS: Serial.println("WL_IDLE_STATUS"); break; 325 | case WL_NO_SSID_AVAIL: Serial.println("WL_NO_SSID_AVAIL"); break; 326 | case WL_SCAN_COMPLETED: Serial.println("WL_SCAN_COMPLETED"); break; 327 | case WL_CONNECT_FAILED: Serial.println("WL_CONNECT_FAILED"); break; 328 | case WL_CONNECTION_LOST: Serial.println("WL_CONNECTION_LOST"); break; 329 | case WL_DISCONNECTED: Serial.println("WL_DISCONNECTED"); break; 330 | } 331 | } 332 | 333 | void Approximate::addActiveDeviceFilter(String macAddress) { 334 | eth_addr macAddress_eth_addr; 335 | String_to_eth_addr(macAddress, macAddress_eth_addr); 336 | 337 | addActiveDeviceFilter(macAddress_eth_addr); 338 | } 339 | 340 | void Approximate::addActiveDeviceFilter(char *macAddress) { 341 | eth_addr macAddress_eth_addr; 342 | c_str_to_eth_addr(macAddress, macAddress_eth_addr); 343 | 344 | addActiveDeviceFilter(macAddress_eth_addr); 345 | } 346 | 347 | void Approximate::addActiveDeviceFilter(Device &device) { 348 | eth_addr macAddress; 349 | device.getMacAddress(macAddress); 350 | 351 | addActiveDeviceFilter(macAddress); 352 | } 353 | 354 | void Approximate::addActiveDeviceFilter(Device *device) { 355 | eth_addr macAddress; 356 | device -> getMacAddress(macAddress); 357 | 358 | addActiveDeviceFilter(macAddress); 359 | } 360 | 361 | void Approximate::addActiveDeviceFilter(int oui) { 362 | eth_addr macAddress; 363 | oui_to_eth_addr(oui, macAddress); 364 | 365 | addActiveDeviceFilter(macAddress); 366 | } 367 | 368 | void Approximate::addActiveDeviceFilter(eth_addr &macAddress) { 369 | Filter *f = new Filter(macAddress); 370 | activeDeviceFilterList.Add(f); 371 | } 372 | 373 | void Approximate::setActiveDeviceFilter(String macAddress) { 374 | removeAllActiveDeviceFilters(); 375 | addActiveDeviceFilter(macAddress); 376 | } 377 | 378 | void Approximate::setActiveDeviceFilter(char *macAddress) { 379 | removeAllActiveDeviceFilters(); 380 | addActiveDeviceFilter(macAddress); 381 | } 382 | 383 | void Approximate::setActiveDeviceFilter(Device &device) { 384 | removeAllActiveDeviceFilters(); 385 | addActiveDeviceFilter(device); 386 | } 387 | 388 | void Approximate::setActiveDeviceFilter(Device *device) { 389 | removeAllActiveDeviceFilters(); 390 | addActiveDeviceFilter(device); 391 | } 392 | 393 | void Approximate::setActiveDeviceFilter(eth_addr &macAddress) { 394 | removeAllActiveDeviceFilters(); 395 | addActiveDeviceFilter(macAddress); 396 | } 397 | 398 | void Approximate::setActiveDeviceFilter(int oui) { 399 | removeAllActiveDeviceFilters(); 400 | addActiveDeviceFilter(oui); 401 | } 402 | 403 | void Approximate::removeActiveDeviceFilter(String macAddress) { 404 | eth_addr macAddress_eth_addr; 405 | String_to_eth_addr(macAddress, macAddress_eth_addr); 406 | 407 | removeActiveDeviceFilter(macAddress_eth_addr); 408 | } 409 | 410 | void Approximate::removeActiveDeviceFilter(Device &device) { 411 | eth_addr macAddress; 412 | device.getMacAddress(macAddress); 413 | 414 | removeActiveDeviceFilter(macAddress); 415 | } 416 | 417 | void Approximate::removeActiveDeviceFilter(Device *device) { 418 | eth_addr macAddress; 419 | device -> getMacAddress(macAddress); 420 | 421 | removeActiveDeviceFilter(macAddress); 422 | } 423 | 424 | void Approximate::removeActiveDeviceFilter(int oui) { 425 | eth_addr macAddress; 426 | oui_to_eth_addr(oui, macAddress); 427 | 428 | removeActiveDeviceFilter(macAddress); 429 | } 430 | 431 | void Approximate::removeActiveDeviceFilter(eth_addr &macAddress) { 432 | for (int n = 0; n < activeDeviceFilterList.Count(); n++) { 433 | Filter *thisFilter = activeDeviceFilterList[n]; 434 | if(thisFilter -> matches(&macAddress)) { 435 | activeDeviceFilterList.Remove(n); 436 | delete thisFilter; 437 | n = 0; //reset the count in case multiple matches 438 | } 439 | } 440 | } 441 | 442 | void Approximate::removeAllActiveDeviceFilters() { 443 | for (int n = 0; n < activeDeviceFilterList.Count(); n++) { 444 | Filter *thisFilter = activeDeviceFilterList[n]; 445 | activeDeviceFilterList.Remove(n); 446 | delete thisFilter; 447 | n = 0; //reset 448 | } 449 | } 450 | 451 | bool Approximate::applyDeviceFilters(Device *device) { 452 | bool result = false; 453 | 454 | for (int n = 0; n < activeDeviceFilterList.Count() && !result; n++) { 455 | Filter *thisFilter = activeDeviceFilterList[n]; 456 | result = thisFilter -> matches(device); 457 | } 458 | 459 | return(result); 460 | } 461 | 462 | void Approximate::setLocalBSSID(String macAddress) { 463 | eth_addr macAddress_eth_addr; 464 | String_to_eth_addr(macAddress, macAddress_eth_addr); 465 | 466 | setLocalBSSID(macAddress_eth_addr); 467 | } 468 | 469 | void Approximate::setLocalBSSID(eth_addr &macAddress) { 470 | ETHADDR16_COPY(&this -> localBSSID, &macAddress); 471 | } 472 | 473 | void Approximate::setActiveDeviceHandler(DeviceHandler activeDeviceHandler, bool inclusive) { 474 | if(!inclusive) { 475 | addActiveDeviceFilter(Filter::NONE); 476 | } 477 | Approximate::activeDeviceHandler = activeDeviceHandler; 478 | } 479 | 480 | void Approximate::setProximateDeviceHandler(DeviceHandler deviceHandler, int rssiThreshold, int lastSeenTimeoutMs) { 481 | setProximateRSSIThreshold(rssiThreshold); 482 | setProximateLastSeenTimeoutMs(lastSeenTimeoutMs); 483 | Approximate::proximateDeviceHandler = deviceHandler; 484 | } 485 | 486 | void Approximate::setProximateRSSIThreshold(int proximateRSSIThreshold) { 487 | Approximate::proximateRSSIThreshold = proximateRSSIThreshold; 488 | } 489 | 490 | void Approximate::setProximateLastSeenTimeoutMs(int proximateLastSeenTimeoutMs) { 491 | Approximate::proximateLastSeenTimeoutMs = proximateLastSeenTimeoutMs; 492 | } 493 | 494 | void Approximate::setChannelStateHandler(ChannelStateHandler channelStateHandler){ 495 | Approximate::channelStateHandler = channelStateHandler; 496 | } 497 | 498 | bool Approximate::parsePacket(wifi_promiscuous_pkt_t *wifi_pkt, uint16_t len, int type, int subtype) { 499 | bool result = false; 500 | 501 | //TODO: is this still needed? 502 | if( wifi_pkt -> rx_ctrl.sig_mode == 1 && len > 512) { 503 | type = PKT_DATA; 504 | } 505 | 506 | switch (type) { 507 | case PKT_MGMT: result = parseMgmtPacket(wifi_pkt); break; 508 | case PKT_CTRL: result = parseCtrlPacket(wifi_pkt); break; 509 | case PKT_DATA: result = parseDataPacket(wifi_pkt, len); break; 510 | case PKT_MISC: result = parseMiscPacket(wifi_pkt); break; 511 | } 512 | 513 | return(result); 514 | } 515 | 516 | bool Approximate::parseCtrlPacket(wifi_promiscuous_pkt_t *wifi_pkt) { 517 | return(false); 518 | } 519 | 520 | bool Approximate::parseMgmtPacket(wifi_promiscuous_pkt_t *wifi_pkt) { 521 | return(false); 522 | } 523 | 524 | bool Approximate::parseDataPacket(wifi_promiscuous_pkt_t *wifi_pkt, uint16_t payloadLengthBytes) { 525 | bool result = false; 526 | 527 | Device *device = new Device(); 528 | if(Approximate::wifi_promiscuous_pkt_to_Device(wifi_pkt, payloadLengthBytes, device)) { 529 | if(!device -> matches(ownMacAddress) && (!onlyIndividualDevices || device -> isIndividual())) { 530 | result = true; 531 | if(proximateDeviceHandler) { 532 | Device *proximateDevice = Approximate::getProximateDevice(device); 533 | int rssi = device -> getRSSI(); 534 | 535 | if(rssi != APPROXIMATE_UNKNOWN_RSSI) { 536 | if(rssi > proximateRSSIThreshold) { 537 | if(proximateDevice) { 538 | //A known proximate device - already in the list 539 | proximateDevice->update(device); 540 | } 541 | else { 542 | //A new proximate device - not already in the list 543 | proximateDevice = new Device(device); 544 | 545 | proximateDeviceList.Add(proximateDevice); 546 | proximateDeviceHandler(proximateDevice, Approximate::ARRIVE); 547 | } 548 | proximateDeviceHandler(proximateDevice, proximateDevice -> isUploading() ? Approximate::SEND : Approximate::RECEIVE); 549 | proximateDevice -> setTimeOutAtMs(millis() + proximateLastSeenTimeoutMs); 550 | } 551 | else { 552 | if(proximateDevice) proximateDevice->update(device); 553 | } 554 | } 555 | } 556 | 557 | if(activeDeviceHandler && (activeDeviceFilterList.IsEmpty() || applyDeviceFilters(device))) { 558 | activeDeviceHandler(device, device -> isUploading() ? Approximate::SEND : Approximate::RECEIVE); 559 | } 560 | } 561 | } 562 | delete(device); 563 | 564 | return(result); 565 | } 566 | 567 | bool Approximate::parseMiscPacket(wifi_promiscuous_pkt_t *pkt) { 568 | return(false); 569 | } 570 | 571 | void Approximate::parseChannelStateInformation(wifi_csi_info_t *info) { 572 | #if defined(ESP32) 573 | if(channelStateHandler) { 574 | Channel *channel = new Channel(); 575 | if(wifi_csi_info_to_Channel(info, channel)) { 576 | //TODO: apply filtering 577 | channelStateHandler(channel); 578 | } 579 | delete channel; 580 | } 581 | #endif 582 | } 583 | 584 | void Approximate::updateProximateDeviceList() { 585 | if(packetSniffer && packetSniffer -> isRunning() && proximateLastSeenTimeoutMs > 0) { 586 | //only update if we have the possibility of new observations 587 | Device *proximateDevice = NULL; 588 | for (int n = 0; n < proximateDeviceList.Count(); n++) { 589 | proximateDevice = proximateDeviceList[n]; 590 | 591 | if(proximateDevice -> hasTimedOut()) { 592 | proximateDeviceHandler(proximateDevice, Approximate::DEPART); 593 | 594 | proximateDeviceList.Remove(n); 595 | delete proximateDevice; 596 | n=0; 597 | } 598 | } 599 | } 600 | } 601 | 602 | bool Approximate::isProximateDevice(Device *device) { 603 | bool result = false; 604 | 605 | if(device) { 606 | eth_addr macAddress_eth_addr; 607 | device -> getMacAddress(macAddress_eth_addr); 608 | result = Approximate::getProximateDevice(macAddress_eth_addr); 609 | } 610 | 611 | return(result); 612 | } 613 | 614 | bool Approximate::isProximateDevice(String macAddress) { 615 | eth_addr macAddress_eth_addr; 616 | String_to_eth_addr(macAddress, macAddress_eth_addr); 617 | 618 | return(isProximateDevice(macAddress_eth_addr)); 619 | } 620 | 621 | bool Approximate::isProximateDevice(eth_addr &macAddress) { 622 | return(Approximate::getProximateDevice(macAddress)); 623 | } 624 | 625 | Device *Approximate::getProximateDevice(Device *device) { 626 | Device *proximateDevice = NULL; 627 | 628 | if(device) { 629 | eth_addr macAddress; 630 | device -> getMacAddress(macAddress); 631 | proximateDevice = getProximateDevice(macAddress); 632 | } 633 | 634 | return(proximateDevice); 635 | } 636 | 637 | Device *Approximate::getProximateDevice(eth_addr &macAddress) { 638 | Device *proximateDevice = NULL; 639 | 640 | //Get known proximate device with this mac address: 641 | for (int n = 0; n < proximateDeviceList.Count() && !proximateDevice; n++) { 642 | if(proximateDeviceList[n] -> matches(macAddress)) { 643 | proximateDevice = proximateDeviceList[n]; 644 | } 645 | } 646 | 647 | return(proximateDevice); 648 | } 649 | 650 | bool Approximate::canResolve() { 651 | return(arpTable != NULL && arpTable->getStatus() == ArpTable::ARP_SCANNED); 652 | } 653 | 654 | bool Approximate::canResolve(ip4_addr_t &ipaddr) { 655 | bool result = false; 656 | 657 | if(arpTable) { 658 | result = arpTable -> contains(ipaddr); 659 | } 660 | 661 | return(result); 662 | } 663 | 664 | bool Approximate::MacAddr_to_eth_addr(MacAddr *in, eth_addr &out) { 665 | bool success = true; 666 | 667 | for(int n=0; n<6; ++n) out.addr[n] = in->mac[n]; 668 | 669 | return(success); 670 | } 671 | 672 | bool Approximate::uint8_t_to_eth_addr(uint8_t *in, eth_addr &out) { 673 | bool success = true; 674 | 675 | for(int n=0; n<6; ++n) out.addr[n] = in[n]; 676 | 677 | return(success); 678 | } 679 | 680 | bool Approximate::oui_to_eth_addr(int oui, eth_addr &out) { 681 | bool success = true; 682 | 683 | out.addr[0] = (oui >> 16) & 0xFF; 684 | out.addr[1] = (oui >> 8) & 0xFF; 685 | out.addr[2] = (oui >> 0) & 0xFF; 686 | out.addr[3] = 0xFF; 687 | out.addr[4] = 0xFF; 688 | out.addr[5] = 0xFF; 689 | 690 | return(success); 691 | } 692 | 693 | bool Approximate::String_to_eth_addr(String &in, eth_addr &out) { 694 | bool success = c_str_to_eth_addr(in.c_str(), out); 695 | 696 | return(success); 697 | } 698 | 699 | bool Approximate::c_str_to_eth_addr(const char *in, eth_addr &out) { 700 | bool success = false; 701 | 702 | //clear: 703 | for(int n=0; n<6; ++n) out.addr[n] = 0; 704 | 705 | //basic format test ##:##:##:##:##:## 706 | if(strlen(in) == 17) { 707 | int a, b, c, d, e, f; 708 | sscanf(in, "%x:%x:%x:%x:%x:%x", &a, &b, &c, &d, &e, &f); 709 | 710 | out.addr[0] = a; 711 | out.addr[1] = b; 712 | out.addr[2] = c; 713 | out.addr[3] = d; 714 | out.addr[4] = e; 715 | out.addr[5] = f; 716 | 717 | success = true; 718 | } 719 | 720 | return(success); 721 | } 722 | 723 | bool Approximate::c_str_to_MacAddr(const char *in, MacAddr &out) { 724 | bool success = false; 725 | 726 | //clear: 727 | for(int n=0; n<6; ++n) out.mac[n] = 0; 728 | 729 | //basic format test ##:##:##:##:##:## 730 | if(strlen(in) == 17) { 731 | int a, b, c, d, e, f; 732 | sscanf(in, "%x:%x:%x:%x:%x:%x", &a, &b, &c, &d, &e, &f); 733 | 734 | out.mac[0] = a; 735 | out.mac[1] = b; 736 | out.mac[2] = c; 737 | out.mac[3] = d; 738 | out.mac[4] = e; 739 | out.mac[5] = f; 740 | 741 | success = true; 742 | } 743 | 744 | return(success); 745 | } 746 | 747 | bool Approximate::eth_addr_to_String(eth_addr &in, String &out) { 748 | bool success = true; 749 | 750 | char macAddressAsCharArray[18]; 751 | eth_addr_to_c_str(in, macAddressAsCharArray); 752 | out = String(macAddressAsCharArray); 753 | 754 | return(success); 755 | } 756 | 757 | bool Approximate::eth_addr_to_c_str(eth_addr &in, char *out) { 758 | bool success = true; 759 | 760 | sprintf(out, "%02X:%02X:%02X:%02X:%02X:%02X\0", in.addr[0], in.addr[1], in.addr[2], in.addr[3], in.addr[4], in.addr[5]); 761 | 762 | return(success); 763 | } 764 | 765 | bool Approximate::MacAddr_to_c_str(MacAddr *in, char *out) { 766 | bool success = true; 767 | 768 | sprintf(out, "%02X:%02X:%02X:%02X:%02X:%02X\0", in->mac[0], in->mac[1], in->mac[2], in->mac[3], in->mac[4], in->mac[5]); 769 | 770 | return(success); 771 | } 772 | 773 | bool Approximate::MacAddr_to_oui(MacAddr *in, int &out) { 774 | bool success = true; 775 | 776 | out = ((in->mac[0] << 16) & 0xFF0000) | ((in->mac[1] << 8) & 0xFF00) | ((in->mac[2] << 0) & 0xFF); 777 | 778 | return(success); 779 | } 780 | 781 | bool Approximate::MacAddr_to_MacAddr(MacAddr *in, MacAddr &out) { 782 | bool success = true; 783 | 784 | for(int n=0; n<6; ++n) out.mac[n] = in -> mac[n]; 785 | 786 | return(success); 787 | } 788 | 789 | bool Approximate::wifi_promiscuous_pkt_to_Device(wifi_promiscuous_pkt_t *wifi_pkt, uint16_t payloadLengthBytes, Device *device) { 790 | bool success = false; 791 | 792 | Packet *packet = new Packet(); 793 | if(wifi_pkt && device && packet) { 794 | wifi_pkt_rx_ctrl_t *rx_ctrl = &(wifi_pkt -> rx_ctrl); 795 | packet -> rssi = rx_ctrl->rssi; 796 | packet -> channel = rx_ctrl->channel; 797 | packet -> payloadLengthBytes = payloadLengthBytes; 798 | 799 | //802.11 packet 800 | wifi_80211_data_frame* frame = (wifi_80211_data_frame*) wifi_pkt -> payload; 801 | MacAddr_to_eth_addr(&(frame -> sa), packet -> src); 802 | MacAddr_to_eth_addr(&(frame -> da), packet -> dst); 803 | 804 | wifi_80211_fctl *fctl = &(frame -> fctl); 805 | byte ds = fctl -> ds; 806 | if(ds == 1 && eth_addr_cmp(&(packet -> dst), &localBSSID)) { 807 | //packet sent by this device 808 | device -> init(packet -> src, localBSSID, packet -> channel, packet -> rssi, millis(), packet -> payloadLengthBytes * -1); 809 | ArpTable::lookupIPAddress(device); 810 | success = true; 811 | } 812 | else if(ds == 2 && eth_addr_cmp(&(packet -> src), &localBSSID)) { 813 | //packet sent to this device - RSSI only informative for messages from device 814 | device -> init(packet -> dst, localBSSID, packet -> channel, packet -> rssi, millis(), packet -> payloadLengthBytes); 815 | ArpTable::lookupIPAddress(device); 816 | success = true; 817 | } 818 | else { 819 | //not associated with this bssid - not on this network 820 | } 821 | } 822 | delete(packet); 823 | 824 | return(success); 825 | } 826 | 827 | bool Approximate::wifi_csi_info_to_Channel(wifi_csi_info_t *info, Channel *channel) { 828 | bool success = false; 829 | 830 | #if defined(ESP32) 831 | if(info->len >= 128) { 832 | eth_addr thisBssid; 833 | uint8_t_to_eth_addr(info -> mac, thisBssid); 834 | 835 | //Filter this network: 836 | if(eth_addr_cmp(&thisBssid, &localBSSID)) { 837 | channel -> setBssid(thisBssid); 838 | channel -> setBuffer(info->buf); 839 | 840 | success = true; 841 | } 842 | } 843 | #endif 844 | 845 | return(success); 846 | } -------------------------------------------------------------------------------- /src/Approximate.h: -------------------------------------------------------------------------------- 1 | /* 2 | Approximate.h 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) October 2020 7 | */ 8 | 9 | #ifndef Approximate_h 10 | #define Approximate_h 11 | 12 | #include 13 | #include "Approximate/eth_addr.h" 14 | #include "Approximate/wifi_pkt.h" 15 | 16 | #include "Approximate/ArpTable.h" 17 | #include "Approximate/Channel.h" 18 | #include "Approximate/Device.h" 19 | #include "Approximate/Filter.h" 20 | #include "Approximate/Network.h" 21 | #include "Approximate/Packet.h" 22 | #include "Approximate/PacketSniffer.h" 23 | 24 | #include //https://github.com/luisllamasbinaburo/Arduino-List 25 | 26 | #define APPROXIMATE_INTIMATE_RSSI -20 27 | #define APPROXIMATE_PERSONAL_RSSI -40 28 | #define APPROXIMATE_SOCIAL_RSSI -60 29 | #define APPROXIMATE_PUBLIC_RSSI -80 30 | 31 | class Approximate { 32 | public: 33 | typedef enum { 34 | PKT_MGMT, 35 | PKT_CTRL, 36 | PKT_DATA, 37 | PKT_MISC, 38 | } PacketType; 39 | 40 | typedef enum { 41 | ARRIVE, 42 | DEPART, 43 | SEND, 44 | RECEIVE, 45 | INACTIVE 46 | } DeviceEvent; 47 | 48 | typedef void (*DeviceHandler)(Device *device, DeviceEvent event); 49 | typedef void (*ChannelStateHandler)(Channel *channel); 50 | 51 | static String toString(DeviceEvent e) { 52 | switch (e) { 53 | case Approximate::SEND: return("SEND"); 54 | case Approximate::RECEIVE: return("RECEIVE"); 55 | case Approximate::ARRIVE: return("ARRIVE"); 56 | case Approximate::DEPART: return("DEPART"); 57 | default: return("INACTIVE"); 58 | } 59 | } 60 | 61 | private: 62 | static bool running; 63 | static bool onlyIndividualDevices; 64 | 65 | static PacketSniffer *packetSniffer; 66 | static ArpTable *arpTable; 67 | 68 | char *ssid = new char[32]; 69 | char *password = new char[64]; 70 | 71 | wl_status_t currentWifiStatus = WL_IDLE_STATUS; 72 | bool initBlind(int channel, uint8_t *bssid, bool ipAddressResolution, bool csiEnabled, bool onlyIndividualDevices); 73 | bool initBlind(bool ipAddressResolution, bool csiEnabled, bool onlyIndividualDevices); 74 | void onWifiStatusChange(wl_status_t oldStatus, wl_status_t newStatus); 75 | 76 | //TODO: template this? 77 | typedef void (*voidFnPtr)(); 78 | typedef void (*voidFnPtrWithStringPayload)(String); 79 | typedef void (*voidFnPtrWithBoolPayload)(bool); 80 | typedef void (*voidFnPtrWithFnPtrPayload)(voidFnPtr); 81 | voidFnPtr onceWifiStatusFnPtr = NULL; 82 | voidFnPtrWithStringPayload onceWifiStatusWithStringPayloadFnPtr = NULL; 83 | voidFnPtrWithBoolPayload onceWifiStatusWithBoolPayloadFnPtr = NULL; 84 | voidFnPtrWithFnPtrPayload onceWifiStatusWithFnPtrPayloadFnPtr = NULL; 85 | String onceWifiStatusStringPayload; 86 | bool onceWifiStatusBoolPayload; 87 | voidFnPtr onceWifiStatusFnPtrPayload; 88 | wl_status_t triggerWifiStatus = WL_IDLE_STATUS; 89 | 90 | static bool parsePacket(wifi_promiscuous_pkt_t *pkt, uint16_t len, int type, int subtype); 91 | static bool parseMgmtPacket(wifi_promiscuous_pkt_t *pkt); 92 | static bool parseCtrlPacket(wifi_promiscuous_pkt_t *pkt); 93 | static bool parseDataPacket(wifi_promiscuous_pkt_t *pkt, uint16_t payloadLength); 94 | static bool parseMiscPacket(wifi_promiscuous_pkt_t *pkt); 95 | 96 | static void parseChannelStateInformation(wifi_csi_info_t *info); 97 | 98 | static DeviceHandler activeDeviceHandler; 99 | static DeviceHandler proximateDeviceHandler; 100 | static ChannelStateHandler channelStateHandler; 101 | 102 | void updateProximateDeviceList(); 103 | 104 | static eth_addr ownMacAddress; 105 | 106 | static eth_addr localBSSID; 107 | static List activeDeviceFilterList; 108 | static bool applyDeviceFilters(Device *device); 109 | 110 | static List proximateDeviceList; 111 | static Device *getProximateDevice(Device *device); 112 | static Device *getProximateDevice(eth_addr &macAddress); 113 | static int proximateRSSIThreshold; 114 | static int proximateLastSeenTimeoutMs; 115 | 116 | void printWiFiStatus(); 117 | 118 | static bool wifi_promiscuous_pkt_to_Device(wifi_promiscuous_pkt_t *pkt, uint16_t payloadLengthBytes, Device *device); 119 | static bool wifi_csi_info_to_Channel(wifi_csi_info_t *info, Channel *channel); 120 | 121 | public: 122 | Approximate(); 123 | bool init(String ssid, String password, bool ipAddressResolution = false, bool csiEnabled = false, bool onlyIndividualDevices = true); 124 | 125 | void begin(voidFnPtr thenFnPtr = NULL); 126 | void end(); 127 | 128 | void loop(); 129 | bool isRunning(); 130 | 131 | //add one more filter 132 | void addActiveDeviceFilter(String macAddress); 133 | void addActiveDeviceFilter(char *macAddress); 134 | void addActiveDeviceFilter(Device &device); 135 | void addActiveDeviceFilter(Device *device); 136 | void addActiveDeviceFilter(eth_addr &macAddress); 137 | void addActiveDeviceFilter(int oui); 138 | 139 | //set exactly one filter 140 | void setActiveDeviceFilter(String macAddress); 141 | void setActiveDeviceFilter(char *macAddress); 142 | void setActiveDeviceFilter(Device &device); 143 | void setActiveDeviceFilter(Device *device); 144 | void setActiveDeviceFilter(eth_addr &macAddress); 145 | void setActiveDeviceFilter(int oui); 146 | 147 | void removeActiveDeviceFilter(String macAddress); 148 | void removeActiveDeviceFilter(Device &device); 149 | void removeActiveDeviceFilter(Device *device); 150 | void removeActiveDeviceFilter(eth_addr &macAddress); 151 | void removeActiveDeviceFilter(int oui); 152 | void removeAllActiveDeviceFilters(); 153 | 154 | void setLocalBSSID(String macAddress); 155 | void setLocalBSSID(eth_addr &macAddress); 156 | 157 | static bool isProximateDevice(Device *device); 158 | static bool isProximateDevice(String macAddress); 159 | static bool isProximateDevice(eth_addr &macAddress); 160 | 161 | bool canResolve(ip4_addr_t &ipaddr); 162 | bool canResolve(); 163 | 164 | void setActiveDeviceHandler(DeviceHandler activeDeviceHandler, bool inclusive = true); 165 | void setProximateDeviceHandler(DeviceHandler deviceHandler, int rssiThreshold = APPROXIMATE_PERSONAL_RSSI, int lastSeenTimeoutMs = 60000); 166 | void setChannelStateHandler(ChannelStateHandler channelStateHandler); 167 | 168 | static void setProximateRSSIThreshold(int proximateRSSIThreshold); 169 | static void setProximateLastSeenTimeoutMs(int proximateLastSeenTimeoutMs); 170 | 171 | wl_status_t connectWiFi(String ssid, String password); 172 | wl_status_t connectWiFi(char *ssid, char *password); 173 | wl_status_t connectWiFi(); 174 | void disconnectWiFi(); 175 | 176 | void onceWifiStatus(wl_status_t status, voidFnPtr callBackFnPtr); 177 | void onceWifiStatus(wl_status_t status, voidFnPtrWithStringPayload callBackFnPtr, String payload); 178 | void onceWifiStatus(wl_status_t status, voidFnPtrWithBoolPayload callBackFnPtr, bool payload); 179 | void onceWifiStatus(wl_status_t status, voidFnPtrWithFnPtrPayload callBackFnPtr, voidFnPtr payload); 180 | 181 | static bool MacAddr_to_eth_addr(MacAddr *in, eth_addr &out); 182 | static bool uint8_t_to_eth_addr(uint8_t *in, eth_addr &out); 183 | static bool oui_to_eth_addr(int oui, eth_addr &out); 184 | static bool c_str_to_eth_addr(const char *in, eth_addr &out); 185 | static bool c_str_to_MacAddr(const char *in, MacAddr &out); 186 | static bool String_to_eth_addr(String &in, eth_addr &out); 187 | static bool eth_addr_to_String(eth_addr &in, String &out); 188 | static bool eth_addr_to_c_str(eth_addr &in, char *out); 189 | static bool MacAddr_to_c_str(MacAddr *in, char *out); 190 | static bool MacAddr_to_oui(MacAddr *in, int &out); 191 | static bool MacAddr_to_MacAddr(MacAddr *in, MacAddr &out); 192 | }; 193 | 194 | #endif -------------------------------------------------------------------------------- /src/Approximate/ArpTable.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ArpTable.cpp 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) October 2020 7 | */ 8 | 9 | #include "ArpTable.h" 10 | 11 | ArpTable::ArpStatus ArpTable::status = ARP_UNSCANNED; 12 | bool ArpTable::running = false; 13 | 14 | ip4_addr_t ArpTable::localNetwork; 15 | uint32_t *ArpTable::cache = new uint32_t[256]; 16 | 17 | #if defined(ESP8266) 18 | const int ArpTable::minUpdateIntervalMs = 300; //updating more frequently is unsafe 19 | #elif defined(ESP32) 20 | const int ArpTable::minUpdateIntervalMs = 50; //updating more frequently is unsafe 21 | #endif 22 | 23 | ArpTable::ArpTable(int updateIntervalMs, bool repeatedScans) { 24 | updateIntervalMs = max(updateIntervalMs, minUpdateIntervalMs); 25 | 26 | this -> updateIntervalMs = updateIntervalMs; 27 | this -> lastUpdateTimeMs = -updateIntervalMs; 28 | 29 | this -> repeatedScans = repeatedScans; 30 | 31 | for(int n=0; n<256; ++n) cache[n] = 0; 32 | } 33 | 34 | ArpTable* ArpTable::getInstance(int updateIntervalMs, bool repeatedScans) { 35 | static ArpTable at(updateIntervalMs, repeatedScans); 36 | return &at; 37 | } 38 | 39 | void ArpTable::begin() { 40 | running = true; 41 | } 42 | 43 | void ArpTable::end() { 44 | running = false; 45 | } 46 | 47 | void ArpTable::loop() { 48 | if(running && WiFi.status() == WL_CONNECTED && millis() > (lastUpdateTimeMs + updateIntervalMs)) { 49 | lastUpdateTimeMs = millis(); 50 | find(scannedDevice, true); 51 | 52 | if((scannedDevice == 255) && !repeatedScans) end(); 53 | else { 54 | scannedDevice = (scannedDevice + 1) % 256; 55 | } 56 | } 57 | } 58 | 59 | bool ArpTable::isRunning() { 60 | return(running); 61 | } 62 | 63 | void ArpTable::scan() { 64 | if(WiFi.status() == WL_CONNECTED) { 65 | status = ARP_SCANNING; 66 | Serial.printf("Building ARP table, takes %i seconds...\t", (minUpdateIntervalMs * 256)/1000); 67 | IP4_ADDR(&localNetwork, WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], 0); 68 | 69 | //initate a full (blocking) scan: 70 | for(int n=0; n<256; ++n) { 71 | find(n, true); 72 | delay(minUpdateIntervalMs); 73 | } 74 | 75 | //write results to the cache: 76 | for(int n=0; n<256; ++n) find(n, false); 77 | 78 | Serial.printf("DONE\n"); 79 | status = ARP_SCANNED; 80 | } 81 | } 82 | 83 | bool ArpTable::find(int localDevice, bool requestIfNotFound) { 84 | ip4_addr_t ipaddr; 85 | ipaddr.addr = (localNetwork.addr & 0xFFFFFF) | (localDevice << 24); 86 | 87 | return(find(ipaddr, requestIfNotFound)); 88 | } 89 | 90 | bool ArpTable::find(ip4_addr_t &ipaddr, bool requestIfNotFound) { 91 | bool found = false; 92 | 93 | struct eth_addr *eth_ret; 94 | const ip4_addr_t *ip_ret; 95 | if(etharp_find_addr(netif_default, &ipaddr, ð_ret, &ip_ret) == -1) { 96 | //unknown 97 | if(requestIfNotFound && WiFi.status() == WL_CONNECTED) { 98 | etharp_request(netif_default, &ipaddr); //if found, ARP table will be updated shortly afterwards 99 | } 100 | } 101 | else { 102 | //known - already in ARP table - add to cache 103 | cache[(ipaddr.addr >> 24) & 0xFF] = getHash(*eth_ret); 104 | found = true; 105 | } 106 | 107 | return(found); 108 | } 109 | 110 | bool ArpTable::lookupIPAddress(Device *device) { 111 | bool success = false; 112 | 113 | if(device) { 114 | eth_addr macAddress; 115 | device -> getMacAddress(macAddress); 116 | 117 | ip4_addr_t ipAddress; 118 | if(lookupIPAddress(macAddress, ipAddress)) { 119 | device -> setIPAddress(ipAddress); 120 | success = true; 121 | } 122 | } 123 | 124 | return(success); 125 | } 126 | 127 | bool ArpTable::lookupIPAddress(eth_addr &macAddress, ip4_addr_t &ipaddr) { 128 | bool found = false; 129 | 130 | uint32_t hash = getHash(macAddress); 131 | for(int n=0; n<256 && !found; ++n) { 132 | if(cache[n] == hash) { 133 | ipaddr.addr = (localNetwork.addr & 0xFFFFFF) | (n << 24); 134 | found = true; 135 | } 136 | } 137 | 138 | if(!found) { 139 | ip4_addr_t ipaddrProbe; 140 | struct eth_addr *eth_ret; 141 | const ip4_addr_t *ip_ret; 142 | for(int n=0; n<256 && !found; ++n) { 143 | ipaddrProbe.addr = (localNetwork.addr & 0xFFFFFF) | (n << 24); 144 | 145 | if(etharp_find_addr(netif_default, &ipaddrProbe, ð_ret, &ip_ret) >= 0) { 146 | if(eth_addr_cmp(&macAddress, eth_ret)) { 147 | ip4_addr_copy(ipaddr, ipaddrProbe); 148 | found = true; 149 | } 150 | } 151 | } 152 | } 153 | 154 | return(found); 155 | } 156 | 157 | bool ArpTable::contains(ip4_addr_t &ipaddr) { 158 | bool result = false; 159 | 160 | if((ipaddr.addr & 0xFFFFFF) == (localNetwork.addr & 0xFFFFFF)) { 161 | //Same subnet 162 | result = (cache[(ipaddr.addr >> 24) & 0xFF] != 0); 163 | } 164 | 165 | return(result); 166 | } 167 | 168 | uint32_t ArpTable::getHash(eth_addr &macAddress) { 169 | uint32_t hash = 0; 170 | 171 | //last 4 bytes: 172 | hash = (macAddress.addr[2] << 24) | (macAddress.addr[3] << 16) | (macAddress.addr[4] << 8) | (macAddress.addr[5] & 0xFF); 173 | 174 | return(hash); 175 | } 176 | 177 | ArpTable::ArpStatus ArpTable::getStatus() { 178 | return(status); 179 | } -------------------------------------------------------------------------------- /src/Approximate/ArpTable.h: -------------------------------------------------------------------------------- 1 | /* 2 | ArpTable.h 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) October 2020 7 | */ 8 | 9 | #ifndef ArpTable_h 10 | #define ArpTable_h 11 | 12 | #include 13 | #include "eth_addr.h" 14 | 15 | #if defined(ESP8266) 16 | #include //https://github.com/esp8266/Arduino 17 | 18 | #elif defined(ESP32) 19 | #include //https://github.com/espressif/arduino-esp32/ 20 | 21 | #endif 22 | 23 | #include "Device.h" 24 | 25 | class ArpTable { 26 | public: 27 | typedef enum { 28 | ARP_UNSCANNED, 29 | ARP_SCANNING, 30 | ARP_SCANNED 31 | } ArpStatus; 32 | 33 | private: 34 | static uint32_t *cache; 35 | static ip4_addr_t localNetwork; 36 | 37 | static bool running; 38 | bool repeatedScans = true; 39 | static ArpStatus status; 40 | 41 | int updateIntervalMs; 42 | static const int minUpdateIntervalMs; 43 | int lastUpdateTimeMs; 44 | 45 | ArpTable(int updateIntervalMs = 500, bool repeatedScans = true); 46 | ArpTable(ArpTable const&); 47 | void operator=(ArpTable const&); 48 | 49 | static bool find(int localDevice, bool requestIfNotFound); 50 | static bool find(ip4_addr_t &ipaddr, bool requestIfNotFound); 51 | int scannedDevice = 0; 52 | 53 | static uint32_t getHash(eth_addr &macAddress); 54 | 55 | public: 56 | static ArpTable* getInstance(int updateIntervalMs = 1000, bool repeatedScans = true); 57 | 58 | void begin(); 59 | void end(); 60 | void loop(); 61 | bool isRunning(); 62 | 63 | static bool lookupIPAddress(Device *device); 64 | static bool lookupIPAddress(eth_addr &macAddress, ip4_addr_t &ipaddr); 65 | bool contains(ip4_addr_t &ipaddr); 66 | 67 | static void scan(); 68 | 69 | ArpTable::ArpStatus getStatus(); 70 | }; 71 | 72 | #endif -------------------------------------------------------------------------------- /src/Approximate/Channel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Channel.cpp 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) February 2021 7 | */ 8 | 9 | #include "Channel.h" 10 | #include "Approximate.h" 11 | 12 | Channel::Channel() { 13 | } 14 | 15 | void Channel::setBuffer(int8_t *buf) { 16 | memcpy(this -> buf, buf, 128 * sizeof(int8_t)); 17 | } 18 | 19 | int8_t Channel::getBufferN(int n) { 20 | int8_t result = 0; 21 | 22 | if(n >= 0 && n < 128) result = buf[n]; 23 | 24 | return(result); 25 | } 26 | 27 | void Channel::getSubCarrier(int n, float &magnitude, float &phase) { 28 | int8_t a = 0, bi = 0; 29 | getSubCarrier(n, a, bi); 30 | 31 | magnitude = sqrt((a * a) + (bi * bi)); 32 | phase = atan2(bi, a); 33 | } 34 | 35 | void Channel::getSubCarrier(int n, int8_t &a, int8_t &bi) { 36 | if(n!=0 && n >= -26 && n <= 26) { 37 | //buffer format: [-,-], [bi, a](1), ... [bi, a](26), [bi, a](-26), ... [bi, a](-1) 38 | int index = (n>0) ? (n*2)+2 : (128-2)+(n*2)+2; 39 | a = buf[index + 1]; 40 | bi = buf[index]; 41 | } 42 | } -------------------------------------------------------------------------------- /src/Approximate/Channel.h: -------------------------------------------------------------------------------- 1 | /* 2 | Channel.h 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) February 2021 7 | */ 8 | 9 | #ifndef Channel_h 10 | #define Channel_h 11 | 12 | #include 13 | #include "Network.h" 14 | #include "eth_addr.h" 15 | 16 | #define APPROXIMATE_UNKNOWN_RSSI 0 17 | 18 | class Channel : public Network { 19 | private: 20 | int8_t buf[128]; 21 | public: 22 | Channel(); 23 | 24 | void setBuffer(int8_t *buf); 25 | int8_t getBufferN(int n); 26 | 27 | void getSubCarrier(int n, float &magnitude, float &phase); 28 | void getSubCarrier(int n, int8_t &a, int8_t &bi); 29 | }; 30 | 31 | #endif -------------------------------------------------------------------------------- /src/Approximate/Device.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Device.cpp 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) October 2020 7 | */ 8 | 9 | #include "Device.h" 10 | #include "Approximate.h" 11 | 12 | Device::Device() { 13 | ipAddress.addr = IPADDR_ANY; 14 | } 15 | 16 | Device::Device(Device *b) { 17 | init(b -> macAddress, b -> bssid, b -> channel, b -> rssi, b -> lastSeenAtMs, b -> dataFlowBytes, b -> ipAddress.addr); 18 | } 19 | 20 | Device::Device(eth_addr &macAddress, eth_addr &bssid, int channel, int rssi, long lastSeenAtMs, int dataFlowBytes, u32_t ipAddress) { 21 | init(macAddress, bssid, channel, rssi, lastSeenAtMs, dataFlowBytes, ipAddress); 22 | } 23 | 24 | //TODO: tidy-up these operators and matches() 25 | bool Device::operator ==(Device *b) { 26 | return(*this == *b); 27 | } 28 | 29 | bool Device::operator ==(Device const& b) { 30 | return(eth_addr_cmp(&this -> macAddress, &b.macAddress)); 31 | } 32 | 33 | bool Device::operator ==(eth_addr &macAddress) { 34 | return(matches(macAddress)); 35 | } 36 | 37 | void Device::init(eth_addr &macAddress, eth_addr &bssid, int channel, int rssi, long lastSeenAtMs, int dataFlowBytes, u32_t ipAddress) { 38 | setMacAddress(macAddress); 39 | setBssid(bssid); 40 | 41 | setChannel(channel); 42 | setRSSI(rssi); 43 | setLastSeenAtMs(lastSeenAtMs); 44 | setDataFlowBytes(dataFlowBytes); 45 | 46 | setIPAddress(ipAddress); 47 | } 48 | 49 | void Device::update(Device *d) { 50 | if(d) init(d -> macAddress, d -> bssid, d -> channel, d -> rssi, d -> lastSeenAtMs, d -> dataFlowBytes, d -> ipAddress.addr); 51 | } 52 | 53 | void Device::getMacAddress(eth_addr &macAddress) { 54 | ETHADDR16_COPY(&macAddress, &this -> macAddress); 55 | } 56 | 57 | String Device::getMacAddressAsString() { 58 | String macAddressAsString = ""; 59 | 60 | Approximate::eth_addr_to_String(macAddress, macAddressAsString); 61 | 62 | return(macAddressAsString); 63 | } 64 | 65 | char *Device::getMacAddressAs_c_str(char *out) { 66 | Approximate::eth_addr_to_c_str(macAddress, out); 67 | 68 | return(out); 69 | } 70 | 71 | void Device::setMacAddress(eth_addr &macAddress) { 72 | ETHADDR16_COPY(&this -> macAddress, &macAddress); 73 | } 74 | 75 | void Device::getIPAddress(ip4_addr_t &ipAddress) { 76 | ipAddress.addr = this -> ipAddress.addr; 77 | } 78 | 79 | String Device::getIPAddressAsString() { 80 | String ipAddressAsString; 81 | ipAddressAsString.reserve(16); 82 | 83 | if(ipAddress.addr != IPADDR_ANY) { 84 | ipAddressAsString = String(ip4addr_ntoa(&ipAddress)); 85 | } 86 | 87 | return(ipAddressAsString); 88 | } 89 | 90 | char *Device::getIPAddressAs_c_str(char *out) { 91 | if(ipAddress.addr != IPADDR_ANY) { 92 | strcpy(out, ip4addr_ntoa(&ipAddress)); 93 | } 94 | 95 | return(out); 96 | } 97 | 98 | void Device::setIPAddress(ip4_addr_t &ipAddress) { 99 | setIPAddress(ipAddress.addr); 100 | } 101 | 102 | void Device::setIPAddress(u32_t ipAddress) { 103 | this -> ipAddress.addr = ipAddress; 104 | } 105 | 106 | bool Device::hasIPAddress() { 107 | return(ipAddress.addr != IPADDR_ANY); 108 | } 109 | 110 | void Device::setRSSI(int rssi) { 111 | this -> rssi = rssi; 112 | } 113 | 114 | int Device::getRSSI(bool uploadOnly) { 115 | int result = APPROXIMATE_UNKNOWN_RSSI; 116 | 117 | if(uploadOnly) result = isUploading() ? rssi : APPROXIMATE_UNKNOWN_RSSI; 118 | else result = rssi; 119 | 120 | return(rssi); 121 | } 122 | 123 | void Device::setLastSeenAtMs(long lastSeenAtMs) { 124 | if(lastSeenAtMs == -1) lastSeenAtMs = millis(); 125 | this -> lastSeenAtMs = lastSeenAtMs; 126 | } 127 | 128 | int Device::getLastSeenAtMs() { 129 | return(lastSeenAtMs); 130 | } 131 | 132 | void Device::setTimeOutAtMs(long timeOutAtMs) { 133 | this -> timeOutAtMs = timeOutAtMs; 134 | } 135 | 136 | void Device::setReducedTimeOutAtMs(long timeOutAtMs) { 137 | setTimeOutAtMs(min(timeOutAtMs, this -> timeOutAtMs)); 138 | } 139 | 140 | bool Device::hasTimedOut() { 141 | return(millis() > timeOutAtMs || timeOutAtMs == -1); 142 | } 143 | 144 | bool Device::matches(eth_addr &macAddress) { 145 | return(eth_addr_cmp(&this -> macAddress, &macAddress)); 146 | } 147 | 148 | uint32_t Device::getOUI() { 149 | uint32_t oui = 0; 150 | 151 | oui = (macAddress.addr[0] << 16) | (macAddress.addr[1] << 8) | (macAddress.addr[2] & 0xFF); 152 | 153 | return(oui); 154 | } 155 | 156 | void Device::setDataFlowBytes(int dataFlowBytes) { 157 | this -> dataFlowBytes = dataFlowBytes; 158 | } 159 | 160 | bool Device::isUploading() { 161 | return(dataFlowBytes < 0); 162 | } 163 | 164 | bool Device::isDownloading() { 165 | return(dataFlowBytes > 0); 166 | } 167 | 168 | int Device::getDownloadSizeBytes() { 169 | int downloadSizeBytes = isDownloading() ? getPayloadSizeBytes() : 0; 170 | 171 | return(downloadSizeBytes); 172 | } 173 | 174 | int Device::getUploadSizeBytes() { 175 | int uploadSizeBytes = isUploading() ? getPayloadSizeBytes() : 0; 176 | 177 | return(uploadSizeBytes); 178 | } 179 | 180 | int Device::getPayloadSizeBytes(){ 181 | return(abs(dataFlowBytes)); 182 | } 183 | 184 | bool Device::isUniversal() { 185 | return(!isLocal()); 186 | } 187 | 188 | bool Device::isIndividual() { 189 | //sometimes there are junk mac addresses where the last half is all zeros 190 | return(!isGroup() && !(macAddress.addr[3] == 0x0 && macAddress.addr[4] == 0x0 && macAddress.addr[5] == 0x0)); 191 | } 192 | 193 | //Universal/local and individual/group defined by: https://standards.ieee.org/content/dam/ieee-standards/standards/web/documents/tutorials/macgrp.pdf 194 | 195 | bool Device::isLocal() { 196 | return((macAddress.addr[0] & 0x2 == 0x2) && !isGroup()); 197 | } 198 | 199 | bool Device::isGroup() { 200 | return(macAddress.addr[0] & 0x1 == 0x1); 201 | } -------------------------------------------------------------------------------- /src/Approximate/Device.h: -------------------------------------------------------------------------------- 1 | /* 2 | Device.h 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) October 2020 7 | */ 8 | 9 | #ifndef Device_h 10 | #define Device_h 11 | 12 | #include 13 | #include "Network.h" 14 | #include "eth_addr.h" 15 | 16 | #define APPROXIMATE_UNKNOWN_RSSI 0 17 | 18 | class Device : public Network { 19 | private: 20 | eth_addr macAddress = {{0,0,0,0,0,0}}; 21 | ip4_addr_t ipAddress; 22 | int rssi = APPROXIMATE_UNKNOWN_RSSI; 23 | long lastSeenAtMs = -1; 24 | int dataFlowBytes = 0; //uploading is negative, downloading positive 25 | 26 | long timeOutAtMs = -1; 27 | 28 | public: 29 | Device(); 30 | Device(Device *b); 31 | Device(eth_addr &macAddress, eth_addr &bssid, int channel, int rssi = APPROXIMATE_UNKNOWN_RSSI, long lastSeenAtMs = -1, int bytesFlow = 0, u32_t ipAddress = IPADDR_ANY); 32 | 33 | //TODO: tidy-up these operators and matches() 34 | bool operator ==(Device *b); 35 | bool operator ==(Device const& b); 36 | bool operator ==(eth_addr &macAddress); 37 | 38 | void init(eth_addr &macAddress, eth_addr &bssid, int channel, int rssi, long lastSeenAtMs, int bytesFlow, u32_t ipAddress = IPADDR_ANY); 39 | void update(Device *d); 40 | 41 | void getMacAddress(eth_addr &macAddress); 42 | String getMacAddressAsString(); 43 | char *getMacAddressAs_c_str(char *out); 44 | void setMacAddress(eth_addr &macAddress); 45 | 46 | void getIPAddress(ip4_addr_t &ipAddress); 47 | String getIPAddressAsString(); 48 | char *getIPAddressAs_c_str(char *out); 49 | void setIPAddress(ip4_addr_t &ipAddress); 50 | void setIPAddress(u32_t ipAddress); 51 | bool hasIPAddress(); 52 | 53 | void setRSSI(int rssi); 54 | int getRSSI(bool uploadOnly = true); 55 | 56 | void setLastSeenAtMs(long lastSeenAtMs = -1); 57 | int getLastSeenAtMs(); 58 | 59 | void setTimeOutAtMs(long timeOutAtMs); 60 | void setReducedTimeOutAtMs(long timeOutAtMs); 61 | bool hasTimedOut(); 62 | 63 | bool matches(eth_addr &macAddress); 64 | 65 | uint32_t getOUI(); 66 | 67 | void setDataFlowBytes(int dataFlowBytes); 68 | 69 | bool isUploading(); 70 | bool isDownloading(); 71 | 72 | int getUploadSizeBytes(); 73 | int getDownloadSizeBytes(); 74 | int getPayloadSizeBytes(); 75 | 76 | bool isUniversal(); 77 | bool isLocal(); 78 | bool isIndividual(); 79 | bool isGroup(); 80 | }; 81 | 82 | #endif -------------------------------------------------------------------------------- /src/Approximate/Filter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Filter.cpp 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) October 2020 7 | */ 8 | 9 | #include "Filter.h" 10 | 11 | eth_addr Filter::NONE = eth_addr({{0xff,0xff,0xff,0xff,0xff,0xff}}); 12 | eth_addr Filter::ANY = eth_addr({{0x00,0x00,0x00,0x00,0x00,0x00}}); 13 | 14 | Filter::Filter(eth_addr &macAddress, Direction direction) { 15 | ETHADDR16_COPY(&this -> macAddress, &macAddress); 16 | 17 | this -> direction = direction; 18 | } 19 | 20 | bool Filter::matches(eth_addr *macAddress) { 21 | bool result = true; 22 | 23 | if(eth_addr_cmp(macAddress, &ANY)) { 24 | result = true; 25 | } 26 | else if(eth_addr_cmp(macAddress, &NONE)) { 27 | result = false; 28 | } 29 | else { 30 | //if an OUI only match on first 3 bytes: 31 | int N = isOUIFilter() ? 3 : 6; 32 | 33 | for(int n=0; n macAddress.addr[n] == macAddress -> addr[n]); 35 | } 36 | } 37 | 38 | return(result); 39 | } 40 | 41 | bool Filter::matches(Device *device) { 42 | bool result = false; 43 | 44 | if(device) { 45 | eth_addr macAddress; 46 | device -> getMacAddress(macAddress); 47 | 48 | if(matches(&macAddress)) { 49 | switch(direction) { 50 | case EITHER: 51 | result = true; break; 52 | case NEITHER: 53 | result = false; break; 54 | case SENDS: 55 | result = device -> isUploading(); 56 | break; 57 | case RECEIVES: 58 | result = device -> isDownloading(); 59 | break; 60 | } 61 | } 62 | } 63 | 64 | return(result); 65 | } 66 | 67 | bool Filter::isOUIFilter() { 68 | bool result = 69 | this -> macAddress.addr[3] == 0xFF && 70 | this -> macAddress.addr[4] == 0xFF && 71 | this -> macAddress.addr[5] == 0xFF; 72 | 73 | return(result); 74 | } -------------------------------------------------------------------------------- /src/Approximate/Filter.h: -------------------------------------------------------------------------------- 1 | /* 2 | Filter.h 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) October 2020 7 | */ 8 | 9 | #ifndef Filter_h 10 | #define Filter_h 11 | 12 | #include 13 | #include "eth_addr.h" 14 | 15 | #include "Device.h" 16 | 17 | class Filter { 18 | public: 19 | typedef enum { 20 | SENDS, 21 | RECEIVES, 22 | EITHER, 23 | NEITHER 24 | } Direction; 25 | 26 | eth_addr macAddress; 27 | Direction direction = Direction::NEITHER; 28 | 29 | static eth_addr NONE; 30 | static eth_addr ANY; 31 | 32 | Filter(eth_addr &macAddress, Direction direction = Direction::EITHER); 33 | bool matches(eth_addr *macAddress); 34 | bool matches(Device *device); 35 | bool isOUIFilter(); 36 | }; 37 | 38 | #endif -------------------------------------------------------------------------------- /src/Approximate/Network.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Network.cpp 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) February 2021 7 | */ 8 | 9 | #include "Network.h" 10 | #include "Approximate.h" 11 | 12 | Network::Network() { 13 | } 14 | 15 | Network::Network(eth_addr &bssid, int channel) { 16 | init(bssid, channel); 17 | } 18 | 19 | void Network::init(eth_addr &bssid, int channel) { 20 | setBssid(bssid); 21 | setChannel(channel); 22 | } 23 | 24 | void Network::getBssid(eth_addr &bssid) { 25 | ETHADDR16_COPY(&bssid, &this -> bssid); 26 | } 27 | 28 | String Network::getBssidAsString() { 29 | String bssidAsString = ""; 30 | 31 | Approximate::eth_addr_to_String(bssid, bssidAsString); 32 | 33 | return(bssidAsString); 34 | } 35 | 36 | char *Network::getBssidAs_c_str(char *out) { 37 | Approximate::eth_addr_to_c_str(bssid, out); 38 | 39 | return(out); 40 | } 41 | 42 | void Network::setBssid(eth_addr &bssid) { 43 | ETHADDR16_COPY(&this -> bssid, &bssid); 44 | } 45 | 46 | int Network::getChannel() { 47 | return(channel); 48 | } 49 | 50 | void Network::setChannel(int channel) { 51 | this -> channel = channel; 52 | } -------------------------------------------------------------------------------- /src/Approximate/Network.h: -------------------------------------------------------------------------------- 1 | /* 2 | Network.h 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) February 2021 7 | */ 8 | 9 | #ifndef Network_h 10 | #define Network_h 11 | 12 | #include 13 | #include "eth_addr.h" 14 | 15 | class Network { 16 | protected: 17 | eth_addr bssid = {{0,0,0,0,0,0}}; 18 | int channel = -1; 19 | 20 | public: 21 | Network(); 22 | Network(eth_addr &bssid, int channel); 23 | void init(eth_addr &bssid, int channel); 24 | 25 | void getBssid(eth_addr &bssid); 26 | String getBssidAsString(); 27 | char *getBssidAs_c_str(char *out); 28 | void setBssid(eth_addr &bssid); 29 | 30 | int getChannel(); 31 | void setChannel(int channel); 32 | }; 33 | 34 | #endif -------------------------------------------------------------------------------- /src/Approximate/Packet.h: -------------------------------------------------------------------------------- 1 | /* 2 | Packet.h 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) October 2020 7 | */ 8 | 9 | #ifndef Packet_h 10 | #define Packet_h 11 | 12 | #include "eth_addr.h" 13 | 14 | class Packet { 15 | public: 16 | eth_addr src = {{0,0,0,0,0,0}}; 17 | eth_addr dst = {{0,0,0,0,0,0}}; 18 | int rssi = 0; 19 | int channel = -1; 20 | uint16_t payloadLengthBytes = 0; 21 | }; 22 | 23 | #endif -------------------------------------------------------------------------------- /src/Approximate/PacketSniffer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | PacketSniffer.cpp 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) October 2020 7 | */ 8 | 9 | #include "PacketSniffer.h" 10 | 11 | PacketSniffer::PacketEventHandler PacketSniffer::packetEventHandler = NULL; 12 | PacketSniffer::ChannelEventHandler PacketSniffer::channelEventHandler = NULL; 13 | bool PacketSniffer::running = false; 14 | 15 | PacketSniffer::PacketSniffer() { 16 | Serial.println("PacketSniffer::PacketSniffer"); 17 | } 18 | 19 | PacketSniffer* PacketSniffer::getInstance() { 20 | Serial.println("PacketSniffer::getInstance"); 21 | static PacketSniffer ps; 22 | return &ps; 23 | } 24 | 25 | bool PacketSniffer::begin() { 26 | if(!running) { 27 | Serial.println("PacketSniffer::begin"); 28 | 29 | #if defined(ESP8266) 30 | wifi_set_opmode(STATION_MODE); //promiscuous works only with STATION_MODE 31 | 32 | wifi_promiscuous_enable(0); 33 | wifi_set_promiscuous_rx_cb(rxCallback_8266); 34 | wifi_promiscuous_enable(1); 35 | 36 | #elif defined(ESP32) 37 | bool CSI_ENABLED = false; 38 | #if defined(CONFIG_ESP32_WIFI_CSI_ENABLED) 39 | CSI_ENABLED = (CONFIG_ESP32_WIFI_CSI_ENABLED == 1) && channelEventHandler; 40 | if(CSI_ENABLED) { 41 | //TODO - This shouldn't be necessary - Approximate::connectWiFi() should handles this as esp_wifi_set_csi() needs, but... 42 | esp_wifi_disconnect(); 43 | delay(1000); 44 | esp_wifi_stop(); 45 | esp_wifi_deinit(); 46 | } 47 | #endif 48 | 49 | tcpip_adapter_init(); 50 | esp_event_loop_init(NULL, NULL); 51 | 52 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 53 | esp_wifi_init(&cfg); 54 | 55 | esp_wifi_set_mode(WIFI_MODE_APSTA); 56 | esp_wifi_start(); 57 | 58 | esp_wifi_set_promiscuous(true); 59 | esp_wifi_set_promiscuous_rx_cb(&rxCallback_32); 60 | 61 | if(CSI_ENABLED && esp_wifi_set_csi(true) == ESP_OK) { 62 | //See: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv424esp_wifi_set_promiscuousb 63 | //WiFi must be initialized by esp_wifi_init() + WiFi must be started by esp_wifi_start() + promiscuous mode must be enabled 64 | 65 | wifi_csi_config_t configuration_csi; 66 | configuration_csi.lltf_en = true; 67 | configuration_csi.htltf_en = true; 68 | configuration_csi.stbc_htltf2_en = true; 69 | configuration_csi.ltf_merge_en = true; 70 | configuration_csi.channel_filter_en = true; 71 | configuration_csi.manu_scale = true; 72 | configuration_csi.shift = 0; // 0->15 73 | 74 | esp_wifi_set_csi_config(&configuration_csi); 75 | esp_wifi_set_csi_rx_cb(&csiCallback_32, NULL); 76 | 77 | Serial.printf("CSI STARTED\n"); 78 | } 79 | 80 | esp_wifi_set_protocol(WIFI_IF_AP, WIFI_PROTOCOL_11B| WIFI_PROTOCOL_11G|WIFI_PROTOCOL_11N); 81 | 82 | #endif 83 | 84 | running = true; 85 | } 86 | 87 | return(running); 88 | } 89 | 90 | void PacketSniffer::end() { 91 | if(running) { 92 | Serial.println("PacketSniffer::end"); 93 | 94 | #if defined(ESP8266) 95 | wifi_promiscuous_enable(0); 96 | 97 | #elif defined(ESP32) 98 | esp_wifi_set_promiscuous(false); 99 | esp_wifi_set_csi(false); 100 | 101 | #endif 102 | 103 | running = false; 104 | } 105 | } 106 | 107 | void PacketSniffer::loop() { 108 | if(running) { 109 | if(channelScan) { 110 | long now = millis(); 111 | if((channelSamplingStartedAtMs + channelSamplingIntervalMs) < now) { 112 | channelSamplingStartedAtMs = now; 113 | 114 | currentChannel += 1; 115 | if(currentChannel > highestChannel) currentChannel = 1; 116 | 117 | setCurrentChannel(currentChannel); 118 | } 119 | } 120 | 121 | if(currentChannel != getCurrentChannel()) { 122 | setCurrentChannel(currentChannel); 123 | } 124 | } 125 | } 126 | 127 | bool PacketSniffer::isRunning() { 128 | return(running); 129 | } 130 | 131 | void PacketSniffer::init(int channel, bool channelScan) { 132 | Serial.println("PacketSniffer::init"); 133 | 134 | currentChannel = channel; 135 | setChannelScan(channelScan); 136 | } 137 | 138 | void PacketSniffer::setCurrentChannel(int channel) { 139 | //Serial.printf("PacketSniffer::setCurrentChannel %i\n", channel); 140 | 141 | #if defined(ESP8266) 142 | if(wifi_set_channel(currentChannel)) { 143 | currentChannel = channel; 144 | } 145 | #elif defined(ESP32) 146 | if(esp_wifi_set_channel(currentChannel, WIFI_SECOND_CHAN_NONE) == ESP_OK) { 147 | currentChannel = channel; 148 | } 149 | #endif 150 | } 151 | 152 | uint8_t PacketSniffer::getCurrentChannel() { 153 | uint8_t currentChannel = -1; 154 | 155 | #if defined(ESP8266) 156 | currentChannel = wifi_get_channel(); 157 | #elif defined(ESP32) 158 | wifi_second_chan_t secondChannel; 159 | esp_wifi_get_channel(¤tChannel, &secondChannel); 160 | #endif 161 | 162 | return(currentChannel); 163 | } 164 | 165 | bool PacketSniffer::getChannelScan() { 166 | return(channelScan); 167 | } 168 | 169 | void PacketSniffer::setChannelScan(bool channelScan) { 170 | this->channelScan = channelScan; 171 | } 172 | 173 | void PacketSniffer::setPacketEventHandler(PacketEventHandler packetEventHandler) { 174 | this -> packetEventHandler = packetEventHandler; 175 | } 176 | 177 | void PacketSniffer::setChannelEventHandler(ChannelEventHandler channelEventHandler) { 178 | this -> channelEventHandler = channelEventHandler; 179 | } 180 | 181 | void PacketSniffer::rxCallback_8266(uint8_t *buf, uint16_t len) { 182 | wifi_promiscuous_pkt_t *packet = (wifi_promiscuous_pkt_t *) buf; 183 | wifi_80211_data_frame *frame = (wifi_80211_data_frame *) (packet -> payload); 184 | wifi_promiscuous_pkt_type_t type = frame->fctl.type; 185 | wifi_mgmt_subtypes_t subtype = frame->fctl.subtype; 186 | 187 | uint16_t sig_len = 0; 188 | #if defined(ESP8266) 189 | sig_len = packet->rx_ctrl.sig_mode ? packet->rx_ctrl.HT_length : packet->rx_ctrl.legacy_length; 190 | #endif 191 | 192 | rxCallback(packet, sig_len, type, subtype); 193 | } 194 | 195 | void PacketSniffer::rxCallback_32(void* buf, wifi_promiscuous_pkt_type_t type) { 196 | wifi_promiscuous_pkt_t *packet = (wifi_promiscuous_pkt_t *) buf; 197 | wifi_80211_data_frame *frame = (wifi_80211_data_frame *) (packet -> payload); 198 | wifi_mgmt_subtypes_t subtype = frame->fctl.subtype; 199 | 200 | uint16_t sig_len = 0; 201 | #if defined(ESP32) 202 | sig_len = packet->rx_ctrl.sig_len; 203 | #endif 204 | 205 | rxCallback(packet, sig_len, type, subtype); 206 | } 207 | 208 | void PacketSniffer::rxCallback(wifi_promiscuous_pkt_t *packet, uint16_t len, wifi_promiscuous_pkt_type_t type, wifi_mgmt_subtypes_t subtype) { 209 | if (running && packetEventHandler) { 210 | packetEventHandler(packet, len, (int) type, subtype); 211 | } 212 | } 213 | 214 | void PacketSniffer::csiCallback_32(void *ctx, wifi_csi_info_t *data) { 215 | if (running && channelEventHandler) { 216 | channelEventHandler(data); 217 | } 218 | } -------------------------------------------------------------------------------- /src/Approximate/PacketSniffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | PacketSniffer.h 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) October 2020 7 | */ 8 | 9 | #ifndef PacketSniffer_h 10 | #define PacketSniffer_h 11 | 12 | #include 13 | #include "eth_addr.h" 14 | #include "wifi_pkt.h" 15 | 16 | class PacketSniffer { 17 | public: 18 | static PacketSniffer* getInstance(); 19 | void init(int channel=1, bool isScanning=false); 20 | bool begin(); 21 | void end(); 22 | void loop(); 23 | bool isRunning(); 24 | 25 | uint8_t getCurrentChannel(); 26 | void setCurrentChannel(int channel); 27 | 28 | bool getChannelScan(); 29 | void setChannelScan(bool channelScan); 30 | 31 | typedef bool (*PacketEventHandler)(wifi_promiscuous_pkt_t *packet, uint16_t len, int type, int subtype); 32 | void setPacketEventHandler(PacketEventHandler packetEventHandler); 33 | 34 | typedef void (*ChannelEventHandler)(wifi_csi_info_t *data); 35 | void setChannelEventHandler(ChannelEventHandler channelEventHandler); 36 | 37 | private: 38 | PacketSniffer(); 39 | PacketSniffer(PacketSniffer const&); 40 | void operator=(PacketSniffer const&); 41 | 42 | static bool running; 43 | 44 | uint8_t currentChannel = -1; 45 | bool channelScan = false; 46 | 47 | int channelSamplingIntervalMs = 1000; 48 | int channelSamplingStartedAtMs = 0; 49 | int highestChannel = 13; //US = 11, EU = 13, Japan = 14 50 | 51 | static void rxCallback_8266(uint8_t *buf, uint16_t len); 52 | static void rxCallback_32(void* buf, wifi_promiscuous_pkt_type_t type); 53 | static void rxCallback(wifi_promiscuous_pkt_t *packet, uint16_t len, wifi_promiscuous_pkt_type_t type, wifi_mgmt_subtypes_t subtype); 54 | 55 | static void csiCallback_32(void *ctx, wifi_csi_info_t *data); 56 | 57 | static PacketEventHandler packetEventHandler; 58 | static ChannelEventHandler channelEventHandler; 59 | }; 60 | 61 | #endif -------------------------------------------------------------------------------- /src/Approximate/eth_addr.h: -------------------------------------------------------------------------------- 1 | /* 2 | eth_addr.h 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) October 2020 7 | */ 8 | 9 | #ifndef eth_addr_h 10 | #define eth_addr_h 11 | 12 | #if defined(ESP8266) 13 | #include "netif/etharp.h" 14 | 15 | #elif defined(ESP32) 16 | #include "lwip/etharp.h" 17 | 18 | #endif 19 | 20 | #ifndef ETHADDR16_COPY 21 | #define ETHADDR16_COPY(src, dst) SMEMCPY(src, dst, ETHARP_HWADDR_LEN) 22 | #endif 23 | 24 | struct __attribute__((packed)) MacAddr { 25 | uint8_t mac[6]; 26 | 27 | bool operator==(const MacAddr& rhs) { 28 | bool result = true; 29 | 30 | for(int n=0; n < 6 && result; ++n) result = (mac[n] == rhs.mac[n]); 31 | 32 | return result; 33 | } 34 | }; 35 | 36 | #endif -------------------------------------------------------------------------------- /src/Approximate/wifi_pkt.h: -------------------------------------------------------------------------------- 1 | /* 2 | wifi_pkt.h 3 | Approximate Library 4 | - 5 | David Chatting - github.com/davidchatting/Approximate 6 | MIT License - Copyright (c) October 2020 7 | */ 8 | 9 | #ifndef wifi_pkt_h 10 | #define wifi_pkt_h 11 | 12 | #if defined(ESP8266) 13 | #include //https://github.com/esp8266/Arduino 14 | 15 | // Expose Espressif SDK functionality 16 | extern "C" { 17 | #include "user_interface.h" 18 | typedef void (*freedom_outside_cb_t)(uint8 status); 19 | int wifi_register_send_pkt_freedom_cb(freedom_outside_cb_t cb); 20 | void wifi_unregister_send_pkt_freedom_cb(void); 21 | int wifi_send_pkt_freedom(uint8 *buf, int len, bool sys_seq); 22 | } 23 | 24 | typedef enum { 25 | WIFI_PKT_MGMT, /**< Management frame, indicates 'buf' argument is wifi_promiscuous_pkt_t */ 26 | WIFI_PKT_CTRL, /**< Control frame, indicates 'buf' argument is wifi_promiscuous_pkt_t */ 27 | WIFI_PKT_DATA, /**< Data frame, indiciates 'buf' argument is wifi_promiscuous_pkt_t */ 28 | WIFI_PKT_MISC /**< Other type, such as MIMO etc. 'buf' argument is wifi_promiscuous_pkt_t but the payload is zero length. */ 29 | } wifi_promiscuous_pkt_type_t; 30 | 31 | typedef struct { 32 | signed rssi: 8; // signal intensity of packet 33 | unsigned rate: 4; 34 | unsigned is_group: 1; 35 | unsigned: 1; 36 | unsigned sig_mode: 2; // 0: non-HT(11bg) packet; 1: HT(11n) packet; 3: VHT(11ac) packet 37 | unsigned legacy_length: 12; // if not 11n packet, shows length of packet. 38 | unsigned damatch0: 1; 39 | unsigned damatch1: 1; 40 | unsigned bssidmatch0: 1; 41 | unsigned bssidmatch1: 1; 42 | unsigned MCS: 7; // if is 11n packet, shows the modulation 43 | // and code used (range from 0 to 76) 44 | unsigned CWB: 1; // if is 11n packet, shows if is HT40 packet or not 45 | unsigned HT_length: 16; // if is 11n packet, shows length of packet. 46 | unsigned Smoothing: 1; 47 | unsigned Not_Sounding: 1; 48 | unsigned: 1; 49 | unsigned Aggregation: 1; 50 | unsigned STBC: 2; 51 | unsigned FEC_CODING: 1; // if is 11n packet, shows if is LDPC packet or not. 52 | unsigned SGI: 1; 53 | unsigned rxend_state: 8; 54 | unsigned ampdu_cnt: 8; 55 | unsigned channel: 4; //which channel this packet in. 56 | unsigned: 12; 57 | } wifi_pkt_rx_ctrl_t; 58 | 59 | typedef struct { 60 | wifi_pkt_rx_ctrl_t rx_ctrl; 61 | u8 payload[0]; // ieee80211 payload 62 | //u16 cnt; // number count of packet 63 | } wifi_promiscuous_pkt_t; 64 | 65 | typedef struct { 66 | } wifi_csi_info_t; 67 | 68 | #elif defined(ESP32) 69 | #define CONFIG_ESP32_WIFI_CSI_ENABLED 1 70 | #define WIFI_MODE WIFI_APSTA_MODE 71 | 72 | #include 73 | #include 74 | #include "esp_wifi.h" 75 | #include "esp_event.h" 76 | #include "esp_wifi_types.h" 77 | 78 | #endif 79 | 80 | typedef enum { 81 | ASSOCIATION_REQ, 82 | ASSOCIATION_RES, 83 | REASSOCIATION_REQ, 84 | REASSOCIATION_RES, 85 | PROBE_REQ, 86 | PROBE_RES, 87 | NU0, 88 | NU1, 89 | BEACON, 90 | ATIM, 91 | DISASSOCIATION, 92 | AUTHENTICATION, 93 | DEAUTHENTICATION, 94 | ACTION, 95 | ACTION_NACK, 96 | } wifi_mgmt_subtypes_t; 97 | 98 | typedef struct { 99 | unsigned vers:2; 100 | wifi_promiscuous_pkt_type_t type:2; 101 | wifi_mgmt_subtypes_t subtype:4; 102 | unsigned ds:2; 103 | unsigned moreFrag:1; 104 | unsigned retry:1; 105 | unsigned pwrMgt:1; 106 | unsigned moreData:1; 107 | unsigned protect:1; 108 | unsigned order:1; 109 | } __attribute__((packed)) wifi_80211_fctl; 110 | 111 | typedef struct { 112 | wifi_80211_fctl fctl; 113 | unsigned duration:16; 114 | MacAddr da; 115 | MacAddr sa; 116 | MacAddr bssid; 117 | int16_t seqctl:16; 118 | unsigned char payload[]; 119 | } __attribute__((packed)) wifi_80211_data_frame; 120 | 121 | //TODO resolve differences with wifi_80211_data_frame: 122 | typedef struct { 123 | wifi_80211_fctl frame_ctrl; 124 | uint8_t addr1[6]; // receiver address 125 | uint8_t addr2[6]; // sender address 126 | uint8_t addr3[6]; // filtering address 127 | unsigned sequence_ctrl:16; 128 | uint8_t addr4[6]; // optional 129 | } wifi_ieee80211_mac_hdr_t; 130 | 131 | typedef struct { 132 | wifi_ieee80211_mac_hdr_t hdr; 133 | uint8_t payload[2]; // network data ended with 4 bytes csum (CRC32) 134 | } wifi_ieee80211_packet_t; 135 | 136 | #endif --------------------------------------------------------------------------------