ESP32 BLE Room Presence with HomeAssistant

I have been using HomeAssistant for a couple years now to automate some smalls things in my life. One of the automations was to turn off my PC monitors and office lights when I wasn't in the room. I started off with a Hue motion sensor which worked well, until I don't move for a duration of time and the lights go off. Very annoying !! Recently I came across this project called ESP32-mqtt-room(https://jptrsn.github.io/ESP32-mqtt-room).

Diving in, I dug out 3 ESP32 boards I had(one for office and one for mancave), and programmed the project to my first board to be the BLE receiver for my Office.

Flashing the firmware was straight forward,I used VSCode with PlatformIO to program the EPS32, but first I had to modify the Settings.h file with my wifi SSID and passphrase along with some MQTT parameters.

//Replace with your Wifi SSID; example: #define ssid "MyWifi"
#define ssid "$SSID$"

//Replace with your Wifi password; example: #define password "12345678"
#define password "$PASS$"

//Replace with a human-friendly host name. Must not contain spaces or special characters and be unique on your network
#define hostname "OfficePresence"

//Replace with your MQTT Broker address; example: #define mqttHost IPAddress(192, 168, 1, 195)
#define mqttHost IPAddress(10 10, 10, 24)

//Replace with your MQTT Broker port; example: #define mqttPort 1883
#define mqttPort 1883

//Replace with your MQTT Broker user; example: #define mqttUser "homeassistant"
#define mqttUser "$MQTT_USER$"

//Replace with your MQTT Broker password; example: #define mqttPassword "12345678"
#define mqttPassword "$MQTT_PASS$"

//Replace with the room name where the node will be placed; example: #define room "living-room"
#define room "Office"

//Specify the LED pin. For most dev boards, this is GPIO2
#define LED_BUILTIN 2

// Logic level for turning the led on. Most boards use active low, meaning LED_ON should be set to 0
#define LED_ON 0

//Define the base topic for room detection. Usually "room_presence"
#define channel "room_presence"

//Define the topic for publishing availability
#define availabilityTopic "presence_nodes/" room

//Define the topic for publishing JSON attributes
#define telemetryTopic "presence_nodes/" hostname "/tele"

// Define bluetooth scan parameters
#define scanInterval 5 // Define the interval in seconds between scans
#define singleScanTime 5 // Define the duration of a single scan in seconds
#define activeScan true // Active scan uses more power, but get results faster
#define bleScanInterval 0x80 // Used to determine antenna sharing between Bluetooth and WiFi. Do not modify unless you are confident you know what you're doing
#define bleScanWindow 0x10 // Used to determine antenna sharing between Bluetooth and WiFi. Do not modify unless you are confident you know what you're doing

// Maximum distance (in meters) to report. Devices that are calculated to be further than this distance in meters will not be reported
#define maxDistance 2

// MQTT topic for sensor values from HTU21D temperature and humidity sensor
//#define htuSensorTopic "presence_nodes/" hostname "/sensor"

Next I added this sensor to HomeAsssitant to make sure it sees the BLE receiver which it does. Now I need a iBeacon transmitter. As the project page suggests a couple options like installing an Android app to act as a iBeacon, I found Android ended up killing the background process and I lost connection. I decided to use one of the ESP32's as a iBeacon. Looking through the example code in the Arduino IDE, I found one called BLE iBeacon.

/*
   Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp
   Ported to Arduino ESP32 by pcbreflux
*/


/*
   Create a BLE server that will send periodic iBeacon frames.
   The design of creating the BLE server is:
   1. Create a BLE Server
   2. Create advertising data
   3. Start advertising.
   4. wait
   5. Stop advertising.
   6. deep sleep

*/
#include "sys/time.h"

#include "BLEDevice.h"
#include "BLEUtils.h"
#include "BLEBeacon.h"
#include "BLEServer.h"
#include "esp_sleep.h"
#include "sys/time.h"

#define GPIO_DEEP_SLEEP_DURATION     10  // sleep x seconds and then wake up
RTC_DATA_ATTR static time_t last;        // remember last boot in RTC Memory
RTC_DATA_ATTR static uint32_t bootcount; // remember number of boots in RTC Memory

#ifdef __cplusplus
extern "C" {
#endif

uint8_t temprature_sens_read();
//uint8_t g_phyFuns;

#ifdef __cplusplus
}
#endif

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
BLEAdvertising *pAdvertising;
struct timeval now;

#define BEACON_UUID           "cfd9353e-3187-4e2e-882e-37a587422830" // UUID 1 128-Bit (may use linux tool uuidgen or random numbers via https://www.uuidgenerator.net/)

void setBeacon() {

  BLEBeacon oBeacon = BLEBeacon();
  oBeacon.setManufacturerId(0x4C00); // fake Apple 0x004C LSB (ENDIAN_CHANGE_U16!)
  oBeacon.setProximityUUID(BLEUUID(BEACON_UUID));
  oBeacon.setMajor((bootcount & 0xFFFF0000) >> 16);
  oBeacon.setMinor(bootcount&0xFFFF);
  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->setAdvertisementData(oAdvertisementData);
  pAdvertising->setScanResponseData(oScanResponseData);

}

void setup() {


  Serial.begin(115200);
  gettimeofday(&now, NULL);

  Serial.printf("start ESP32 %d\n",bootcount++);

  Serial.printf("deep sleep (%lds since last reset, %lds since last boot)\n",now.tv_sec,now.tv_sec-last);

  last = now.tv_sec;

  // Create the BLE Device
  BLEDevice::init("");

  // Create the BLE Server
  // BLEServer *pServer = BLEDevice::createServer(); // <-- no longer required to instantiate BLEServer, less flash and ram usage

  pAdvertising = BLEDevice::getAdvertising();

  setBeacon();
   // Start advertising
  pAdvertising->start();
  Serial.println("Advertizing started...");
  delay(100);
  pAdvertising->stop();
  Serial.printf("enter deep sleep\n");
  esp_deep_sleep(1000000LL * GPIO_DEEP_SLEEP_DURATION);
  Serial.printf("in deep sleep\n");
}

void loop() {
}

I needed to make a couple changes for this to work. First I had to change the BEACON_UUID, I used the unix command uuidgen to create a random UUID. That was pretty much it at first. After some testing I noticed the sensor wasn't seen after first time. Loading up an Android App called nRF Connect(this allows you to view nearby bluetooth beacons), I noticed that the minor ID was changing on every update. Looking at the arduino example code, I modified the following:

Before

  oBeacon.setMajor((bootcount & 0xFFFF0000) >> 16);
  oBeacon.setMinor(bootcount&0xFFFF);
 ```

  After
  ```C++
  oBeacon.setMajor(0);
  oBeacon.setMinor(3);

This statically sets the Major and Minor versions which you have to use in HomeAssistant in the device_id. Next I programmed a second recevier to put in my mancave, I would like to control the light in that room with my presence as well. Last step is to write some new flows in Node-Red to create the light automation based on BLE presence apposed to motion detection.

HomeAssistant

HomeAssistant

After further testing, I found the BeaconScope Android app to be reliable after disabling Power Saver in Android battery Settings.


Last update: January 27, 2022