The Approximate Library is a WiFi Arduino library for building proximate interactions between your Internet of Things and the ESP8266 or ESP32

Related tags

Arduino Approximate
Overview

esp8266 esp32

The Approximate Library

The Approximate library is a WiFi Arduino Library for building proximate interactions between your Internet of Things and the ESP8266 or ESP32, perhaps a switch that operates the nearest lamp or a song that plays when you (and your phone) come home.

CloseBySonoff example

Technically this library 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.

Installation

The latest stable release of Approximate is available in the Arduino IDE Library Manager - search for "Approximate". Click install.

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.

Approximate requires that either the Arduino core for the ESP8266 or ESP32 is installed - follow these instructions:

In addition, the following libraries are also required:

Limitations

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 is enabled - the default iOS setting.

Examples

This section describes in some technical detail each of the examples available from the Examples menu once Approximate is correctly installed.

Every Approximate sketch has this essential structure:

#include <Approximate.h>
Approximate approx;

void setup() {
    if (approx.init("MyHomeWiFi", "password")) {
        approx.begin();
    }
}

void loop() {
    approx.loop();
}

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.

When We're Close... using a Proximate Device Handler

CloseBy example

The CloseBy example indicates when a WiFi device is in proximity and prints out its MAC addresses.

#include <Approximate.h>
Approximate approx;

const int LED_PIN = 2;

void setup() {
    Serial.begin(9600);
    pinMode(LED_PIN, OUTPUT);

    if (approx.init("MyHomeWiFi", "password")) {
        approx.setProximateDeviceHandler(onProximateDevice, APPROXIMATE_PERSONAL_RSSI);
        approx.begin();
    }
}

void loop() {
    approx.loop();
}

void onProximateDevice(Device *device, Approximate::DeviceEvent event) {
    switch(event) {
        case Approximate::ARRIVE:
            digitalWrite(LED_PIN, HIGH);
            Serial.println("ARRIVE\t" + device->getMacAddressAsString());
            break;
        case Approximate::DEPART:
            digitalWrite(LED_PIN, LOW);
            Serial.println("DEPART\t" + device->getMacAddressAsString());
            break;
    }
}

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 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.

There are four event types that a DeviceHandler will encounter:

  • Approximate::ARRIVE once when the device first arrives in proximity (only for Proximate Device Handlers)
  • Approximate::DEPART once when the device departs and is no longer seen in proximity (only for Proximate Device Handlers)
  • Approximate::SEND every time the device sends (uploads) data
  • Approximate::RECEIVE every time the device receives (downloads) data (rarely for Proximate Device Handlers, unless the router is also in proximity)

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 is a measure of WiFi signal strength used to estimate proximity. It is measured in 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:

  • APPROXIMATE_INTIMATE_RSSI -20 dBm (10 centimetres)
  • APPROXIMATE_PERSONAL_RSSI -40 dBm (1 metre)
  • APPROXIMATE_SOCIAL_RSSI -60 dBm (4 metres)
  • APPROXIMATE_PUBLIC_RSSI -80 dBm (8 metres)

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:

void setProximateDeviceHandler(DeviceHandler deviceHandler, int rssiThreshold = APPROXIMATE_PERSONAL_RSSI, int lastSeenTimeoutMs = 60000);

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.

Find My... using an Active Device Handler

FindMy example

The FindMy example demonstrates how a device can be found on your WiFi network using its signal strength (as measured by RSSI) - the LED flashing increases as the distance decreases.

#include <Approximate.h>
Approximate approx;

const int LED_PIN = 2;
bool ledState = LOW;
long ledToggleAtMs = 0;
int ledToggleIntervalMs = 0;

void setup() {
  Serial.begin(9600);
  pinMode(LED_PIN, OUTPUT);

  if (approx.init("MyHomeWiFi", "password")) {
    approx.setActiveDeviceFilter("XX:XX:XX:XX:XX:XX");
    approx.setActiveDeviceHandler(onActiveDevice);
    approx.begin();
  }
}

void loop() {
  approx.loop();

  digitalWrite(LED_PIN, ledState);
  
  if(ledToggleIntervalMs > 0 && millis() > ledToggleAtMs) {
    ledState = !ledState;
    ledToggleAtMs = millis() + ledToggleIntervalMs;
  }
}

void onActiveDevice(Device *device, Approximate::DeviceEvent event) {
  if(event == Approximate::SEND) {  
    ledToggleIntervalMs = map(device->getRSSI(), -100, 0, 1000, 0);
  }
}

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.

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:

void setActiveDeviceHandler(DeviceHandler activeDeviceHandler, bool inclusive = true);

Unlike the Proximate Device Handler the Active Device Handler is not filtered by signal strength, but instead can be filtered by MAC address or by device manufacturer with an OUI code. 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. 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.

void setActiveDeviceFilter(String macAddress);
void setActiveDeviceFilter(int oui);

void addActiveDeviceFilter(String macAddress);
void addActiveDeviceFilter(int oui);

Watch Device - using a Proximate Device Handler and an Active Device Handler

WatchDevice example

The WatchDevice example 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.

#include <Approximate.h>
Approximate approx;

const int LED_PIN = 2;
long ledOnUntilMs = 0;

void setup() {
  Serial.begin(9600);
  pinMode(LED_PIN, OUTPUT);

  if (approx.init("MyHomeWiFi", "password")) {
    approx.setProximateDeviceHandler(onProximateDevice, APPROXIMATE_INTIMATE_RSSI, /*lastSeenTimeoutMs*/ 1000);
    approx.setActiveDeviceHandler(onActiveDevice, /*inclusive*/ false);
    approx.begin();
  }
}

void loop() {
  approx.loop();
  digitalWrite(LED_PIN, millis() < ledOnUntilMs);
}

void onProximateDevice(Device *device, Approximate::DeviceEvent event) {
  switch (event) {
    case Approximate::ARRIVE:
      Serial.println("Watching:  " + device -> getMacAddressAsString());
      approx.setActiveDeviceFilter(device);
      break;
    case Approximate::DEPART:
      break;
  }
}

void onActiveDevice(Device *device, Approximate::DeviceEvent event) {
    if(event == Approximate::RECEIVE) {
      ledOnUntilMs = millis() + (device -> getPayloadSizeBytes()/10);
    }
}

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.

Close By MQTT - make a connection to the cloud

CloseByMQTT example

The CloseByMQTT example makes a connection to the cloud when a device is in proximity, sending a message to an MQTT server. It requires the Arduino Client for MQTT library - search for "pubsubclient" in the Arduino IDE Library Manager.

#include <Approximate.h>
#include <PubSubClient.h>

Approximate approx;

WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);

const int LED_PIN = 2;

void setup() {
  Serial.begin(9600);
  pinMode(LED_PIN, OUTPUT);

  if (approx.init("MyHomeWiFi", "password")) {
    approx.setProximateDeviceHandler(onProximateDevice);
    approx.begin([]() {
      mqttClient.setServer("192.168.XXX.XXX", 1883);
    });
  }
}

void loop() {
  approx.loop();
  mqttClient.loop();
}

void onProximateDevice(Device *device, Approximate::DeviceEvent event) {
  if(event == Approximate::ARRIVE || event == Approximate::DEPART) {
    digitalWrite(LED_PIN, event == Approximate::ARRIVE);

    String json = "{\"" + device->getMacAddressAsString() + "\":\"" + Approximate::toString(event) + "\"}";
    Serial.println(json);
    
    approx.onceWifiStatus(WL_CONNECTED, [](String payload) {
      mqttClient.connect(WiFi.macAddress().c_str());
      mqttClient.publish("closeby", payload.c_str(), false); //false = don't retain message
      
      #if defined(ESP8266)
        delay(20);
        approx.disconnectWiFi();
      #endif
    }, json);
    approx.connectWiFi();
  }
}

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.

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.

  approx.onceWifiStatus(WL_CONNECTED, []() {
    Serial.println("The WiFi is connected!");
  });
  approx.connectWiFi();

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.

Close By Sonoff - interacting with devices

CloseBySonoff example

The CloseBySonoff example demonstrates an interaction with a proximate device - namely the Sonoff BASICR3 smart switch. In DIY Mode 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 library - available via the Arduino IDE Library Manager.

#include <Approximate.h>
Approximate approx;

#include <HTTPClient.h>

#include <AceButton.h>
using namespace ace_button;

const int LED_PIN = 2;
const int BUTTON_PIN = 0;
AceButton button(BUTTON_PIN);

Device *closeBySonoff = NULL;

void setup() {
  Serial.begin(9600);

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, false);

  ButtonConfig* buttonConfig = button.getButtonConfig();
  buttonConfig->setEventHandler(onButtonEvent);

  if (approx.init("MyHomeWiFi", "password", true)) {
    approx.setProximateDeviceHandler(onProximateDevice, APPROXIMATE_SOCIAL_RSSI, 10000);
    approx.begin();
  }
}

void loop() {
  approx.loop();
  button.check();
}

void onProximateDevice(Device *device, Approximate::DeviceEvent event) {
  switch (device->getOUI()) {
    //D8F15B Sonoff (Expressif Inc) - see: http://standards-oui.ieee.org/oui.txt
    case 0xD8F15B:
      onCloseBySonoff(device, event);
      break;
  }
}

void onCloseBySonoff(Device *device, Approximate::DeviceEvent event) {
  switch (event) {
    case Approximate::ARRIVE:
      closeBySonoff = device;
      digitalWrite(LED_PIN, HIGH);
      break;
    case Approximate::DEPART:
      if(*device == *closeBySonoff) {
        closeBySonoff = NULL;
        digitalWrite(LED_PIN, LOW);
      }
      break;
  }
}

void onButtonEvent(AceButton* button, uint8_t eventType, uint8_t buttonState) {
  if(closeBySonoff) {  
    switch (eventType) {
      case AceButton::kEventPressed:
        switchCloseBySonoff(true);
        break;
      case AceButton::kEventReleased:
        switchCloseBySonoff(false);
        break;
    }
  }
}

void switchCloseBySonoff(bool switchState) {
  if(closeBySonoff) {
    approx.onceWifiStatus(WL_CONNECTED, [](bool switchState) {
      if(closeBySonoff) {
        HTTPClient http;
        String url = "http://" + closeBySonoff->getIPAddressAsString() + ":8081/zeroconf/switch";
        http.begin(url);
        http.addHeader("Content-Type", "application/json");
      
        String switchValue = switchState?"on":"off";
        String httpRequestData = "{\"deviceid\": \"\",\"data\": {\"switch\": \"" + switchValue + "\"}}";
        
        int httpResponseCode = http.POST(httpRequestData);
        Serial.printf("%s\t%s\t%i\n",url.c_str(), httpRequestData.c_str(), httpResponseCode);
        http.end();
      }
      
      #if defined(ESP8266)
        delay(20);
        approx.disconnectWiFi();
      #endif
    }, switchState);
    approx.connectWiFi();
  }
}

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. 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.

Significantly this example requires that not only a proximate device's MAC address be known, but also its local IP address - 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 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.

In Use

Projects that use the Approximate library include:

Credits

The Approximate library has learnt much from the work of others, including:

Author

The Approximate library was created by David Chatting (@davidchatting) as part of the A Network of One's Own project. Collaboration welcome - please contribute by raising issues and making pull requests via GitHub. This code is licensed under the MIT License.

You might also like...
Anto client library for ESP8266-Arduino

Anto client library for ESP8266-Arduino ESP8266-AntoIO provides common and easy way to connect your ESP8266 to Anto.io IoT platform service. Stable ve

♾ The All-New AllThingsTalk Arduino SDK for WiFi Devices
♾ The All-New AllThingsTalk Arduino SDK for WiFi Devices

AllThingsTalk Arduino WiFi SDK AllThingsTalk Arduino Library for WiFi Devices - makes connecting your devices with your AllThingsTalk Maker a breeze.

Arduino polyphonic synthesizer project (not a Moog) for ESP32 - STM32 - Teensy and more
Arduino polyphonic synthesizer project (not a Moog) for ESP32 - STM32 - Teensy and more

ml_synth_basic_example Arduino polyphonic synthesizer project (not a Moog) for ESP32 - STM32 - Teensy and more link to the video My intention was to m

Arduino Arduino library for the CloudStorage server project. The library provides easy access to server-stored values and operations.

Arduino-CloudStorage Arduino/ESP8266 library that allows you to easly store and retreive data from a remote (cloud) storage in a key/value fashion. Cl

A library to simplify the process of getting and storing data to Antares IoT Platform through HTTP on ESP8266.
A library to simplify the process of getting and storing data to Antares IoT Platform through HTTP on ESP8266.

Antares ESP8266 HTTP This is the documentation for Antares ESP8266 library. This library is meant to simplify the process of retrieving and deploying

A library to simplify the process of subscribing and publishing data to Antares IoT Platform through MQTT on ESP8266.
A library to simplify the process of subscribing and publishing data to Antares IoT Platform through MQTT on ESP8266.

Antares ESP8266 MQTT A Library to simplify the process of MQTT publication and subscription to Antares IoT Platform using ESP8266. This library works

Arduino library for making an IHC in or output module using an Arduino

Introduction This is an Arduino library for making an IHC in or output module using an Arduino. (IHC controller is a home automation controller made b

ArduinoIoTCloud library is the central element of the firmware enabling certain Arduino boards to connect to the Arduino IoT Cloud

ArduinoIoTCloud What? The ArduinoIoTCloud library is the central element of the firmware enabling certain Arduino boards to connect to the Arduino IoT

Library code for Adafruit's CC3000 WiFi breakouts &c

Adafruit CC3000 Library This is a library for the Adafruit CC3000 WiFi Breakouts etc Designed specifically to work with the Adafruit CC3000 Breakout -

Comments
  • Unnecessary departs?

    Unnecessary departs?

    I used the closeby example and extended it a little to switch a Sonoff relay on and off: approx.setProximateDeviceHandler(onProximateDevice, APPROXIMATE_PERSONAL_RSSI, 5000);

    and

    switch (event) { case Approximate::ARRIVE: Serial.println("ARRIVE\t" + device->getMacAddressAsString()); if (device->getMacAddressAsString() == macaddress) { digitalWrite(LED_PIN, HIGH); Serial.println("**ARRIVE\t" + device->getMacAddressAsString()); } break; case Approximate::DEPART: Serial.println("DEPART\t" + device->getMacAddressAsString()); if (device->getMacAddressAsString() == macaddress) { digitalWrite(LED_PIN, LOW); Serial.println("**DEPART\t" + device->getMacAddressAsString()); } break; }

    Even if the Smartphone is very close to the Sonoff, it creates a lot of departure messages. I set the timer to 5000 to get a fast reaction for a demonstration. It would be good if we would get RSSI values in Serial for debugging...

    opened by SensorsIot 18
  • Approximate Distance

    Approximate Distance

    Hello, first off, great library

    I wanted to know if there is a way to get approximate distance? i understand from this section you described the approximate RSSI and their corresponding approximate distance, i believe the library uses rssi strength to determine the proximity of the other device. Does the library exposes a function to get the approximate distance?

    opened by DonnC 1
  • Fix GH Action for testing library examples

    Fix GH Action for testing library examples

    Added a few more libs you needed, but more importantly using your fork of the List library. If your PR is accepted, you can add the library back to the list of libraries in the main action step, and get rid of the custom lib checkout!

    opened by mikevanis 0
  •  802.11n packets incorrectly parsed [was Payload limit for MacOS 10.15 devices (at least) - large payloads unseen]

    802.11n packets incorrectly parsed [was Payload limit for MacOS 10.15 devices (at least) - large payloads unseen]

    For my MacBook MacOS 10.15 (at least) I don't see large packets addressed to its MAC address - I only see payloads of 28 bytes in length - however, it does seem to work properly for iOS devices. Other packets that may be asscoiated with this target device and have larger payloads are parsed with different MAC addresses, so this may be a problem with the parsing or (more likely) a misunderstaning of how MacOS addresses packets. This is an issue for both the ESP8266 and ESP32.

    bug esp8266 esp32 
    opened by davidchatting 4
Releases(v1.5)
Owner
David Chatting
David Chatting
Arduino, esp32 and esp8266 library for ABB (ex PowerOne) Aurora Inverter, implement a full methods to retrieve data from the Inverter via RS-485

ABB Aurora protocol You can refer the complete documentation on my site ABB Aurora PV inverter library for Arduino, esp8266 and esp32 I create this li

Renzo Mischianti 22 Nov 22, 2022
A library for writing modern websockets applications with Arduino (ESP8266 and ESP32)

Arduino Websockets A library for writing modern websockets applications with Arduino (see prerequisites for supported platforms). This project is base

Gil Maimon 338 Dec 2, 2022
IOTBOT, which is designed as an Internet-oriented robotic coding training kit and powered by the ESP32 processor

IOTBOT-Firmware! Test Series IOTBOT, which is designed as an Internet-oriented robotic coding training kit and powered by the ESP32 processor, knows n

null 1 Dec 29, 2021
Library for ESP32 and ESP8266 to work with the Fernando K app

App Fernando K This library is meant to work with the Fernando K app https://play.google.com/store/apps/details?id=com.appfernandok https://apps.apple

null 0 Aug 5, 2020
Simple web interface builder for esp8266 and ESP32

GyverPortal Простой конструктор веб интерфейса для esp8266 и ESP32 Простой конструктор - делаем страницы без знаний HTML и CSS Библиотека является обё

Alex 181 Nov 27, 2022
ESP32 Temp Alarm using DS18B20, wifi manager, Email alert Threshold

ESP32-Freezer-Alarm ESP32 Temp Alarm using DS18B20, wifi manager, Email alert Threshold. I made this alarm using several tutorials from https://random

null 5 Oct 7, 2022
Arduino library to access Adafruit IO from WiFi, cellular, and ethernet modules.

Adafruit IO Arduino Library This library provides a simple device independent interface for interacting with Adafruit IO using Arduino. It allows you

Adafruit Industries 166 Nov 9, 2022
Arduino library for sending email and SMS from nothing but the ESP8266!

Did you know your ESP8266 could send Email and SMS without any special hardware or paid services like Twilio? With AlertMe, your ESP8266 project can:

Lixie Labs 61 Feb 24, 2022
This Arduino IDE for ArduCAM ESP8266 UNO Board with Integrated ArduCAM Library and Examples

ArduCAM_ESP8266_UNO Please use josn board manager script from http://www.arducam.com/downloads/ESP8266_UNO/package_ArduCAM_index.json to download ESP8

Lee 82 Sep 3, 2022
Library for Arduino UNO WiFi Developer Edition

Note: This library will no longer be maintained by Arduino. Uno WiFi Developer Edition Library Library for Arduino Uno WiFi Developer Edition For more

Arduino Libraries 19 Feb 7, 2022