This is the current prototype sketch for the ESP32-C3-DevKitM-1, which is based on the ESP32-C3-MINI-1 module. I'm using the Arduino IDE, simply because it had the lowest entry hurdle. I usually use a baremetal SDK or toolchain and Eclipse as the IDE but the Arduino framework has something going for it, which is it allows to quickly code up simple stuff.
The functionality implemented is quite simple but I don't think I will need more.
The sketch initializes a beacon and starts advertising it and configures a passive scan.
In the main loop, it scans for two seconds (passive scan), then evaluates the scan results, then goes to sleep for one second to save battery.
If a beacon was received during the 2 seconds, it simply goes back to sleep.
If a beacon was not received for 8 seconds, it enters "ALARM" mode. This is when the buzzer on both devices would go off. For now only the RGB LED on the board turns from green to red.
Both devices stay in ALARM mode until they receive a beacon with sufficient signal strength (-60 dBm). That is roughly equivalent to being an arms length away. So the buzzer would keep sounding until I was near to my son again.
I made an experiment today with my little daughter, asked her to walk away from me with the "don't go too far away"-device until the light turned red and then come back. The range in free area is quite remarkable, maybe 50 meters or more. I expected it to be less, but I can still introduce an artificial RSSI limit.
I haven't yet checked power consumption or optimized anything. This evaluation will decide whether I'll just use an ESP32 module or go for a fully custom design.
/*
Based on Neil Kolban example for IDF: [url]https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp[/url]
Ported to Arduino ESP32 by pcbreflux
*/
#include "BLEDevice.h"
#include "BLEUtils.h"
#include "BLEScan.h"
#include "BLEAdvertisedDevice.h"
#include "BLEBeacon.h"
#include "Adafruit_NeoPixel.h"
#include "esp_sleep.h"
#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00) >> 8) + (((x)&0xFF) << 8))
static BLEAdvertising *pAdvertising;
static BLEScan *pBLEScan;
// See the following for generating UUIDs:
// [url]https://www.uuidgenerator.net/[/url]
#define BEACON_UUID "94e82029-3762-45f2-befb-7fd39711751f" // UUID 1 128-Bit (may use linux tool uuidgen or random numbers via [url]https://www.uuidgenerator.net/[/url])
#define SERVICE_UUID "c9a70b84-027e-4cf5-b8d5-708f1f9956a1"
// NeoPixel
#define NEO_PIN 8
#define NEO_NUM 1
Adafruit_NeoPixel pixel(NEO_NUM, NEO_PIN, NEO_GRB + NEO_KHZ800);
static void setScan()
{
pBLEScan = BLEDevice::getScan(); //create new scan
}
static void setBeacon()
{
BLEBeacon oBeacon = BLEBeacon();
oBeacon.setManufacturerId(ENDIAN_CHANGE_U16(0xDEAF));
oBeacon.setProximityUUID(BLEUUID(BEACON_UUID));
oBeacon.setMajor(0);
oBeacon.setMinor(0);
oBeacon.setSignalPower(-50);
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
BLEAdvertisementData oScanResponseData = BLEAdvertisementData();
oAdvertisementData.setFlags(0x04); // BR_EDR_NOT_SUPPORTED 0x04
std::string strServiceData = "";
strServiceData += (char)26; // Len
strServiceData += (char)0xFF; // Type
strServiceData += oBeacon.getData();
oAdvertisementData.addData(strServiceData);
pAdvertising = BLEDevice::getAdvertising();
pAdvertising->setAdvertisementData(oAdvertisementData);
pAdvertising->setScanResponseData(oScanResponseData);
pAdvertising->setAdvertisementType(ADV_TYPE_NONCONN_IND);
// set advertising intervals
pAdvertising->setMinInterval(500);
pAdvertising->setMaxInterval(1000);
pAdvertising->setMinPreferred(500);
pAdvertising->setMaxPreferred(800);
// Start advertising
pAdvertising->start();
}
void setup()
{
Serial.begin(115200);
// Create the BLE Device
BLEDevice::init("ESP32 as iBeacon");
// configure beacon and scan
setBeacon();
setScan();
// clear the pixel for now
pixel.begin();
pixel.clear();
}
void loop()
{
static int alarmState = -1;
static int lastRssi = 0;
static int lastSeen = 0;
bool beaconFound = false;
// start a 2 second passive scan
BLEScanResults foundDevices = pBLEScan->start(2, false);
// check what we found
for (int i = 0; i < foundDevices.getCount(); ++i) {
BLEAdvertisedDevice advertisedDevice = foundDevices.getDevice(i);
if (advertisedDevice.haveManufacturerData() == true) {
std::string strManufacturerData = advertisedDevice.getManufacturerData();
uint8_t cManufacturerData[100];
strManufacturerData.copy((char *)cManufacturerData, strManufacturerData.length(), 0);
if (strManufacturerData.length() == 25 && cManufacturerData[0] == 0xAF && cManufacturerData[1] == 0xDE) {
lastRssi = advertisedDevice.getRSSI();
beaconFound = true;
}
}
}
pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory
if (beaconFound) {
lastSeen = 0;
// call off alarm only when in near vicinity
if ((alarmState < 0 || alarmState == 1) && lastRssi > -60) {
pixel.setPixelColor(0, 0, 32, 0);
pixel.show();
alarmState = 0;
}
} else {
++lastSeen;
if ((alarmState <= 0) && lastSeen > 3) {
pixel.setPixelColor(0, 32, 0, 0);
pixel.show();
alarmState = 1;
}
}
Serial.printf("Beacon last seen %i cycles ago with RSSI %i dBm, ALARM %s\n",
lastSeen, lastRssi, alarmState == 1 ? "ON":"OFF");
// flush serial before going to sleep
Serial.flush();
// enter light sleep for one seconds
esp_sleep_enable_timer_wakeup(1000 * 1000);
esp_light_sleep_start();
}