Idea - BLE Shelly Button 1 as Arrival Sensor

Home Arrival Sensor idea.
I came across Shelly Button 1 BLE (not Wifi) device and ordered few right away (I like new toys).

Since this is BLE version there is no HE direct support for these buttons.
I am hopping to use it as an Home Arrival Sensor paired to Home Assistant and integrated
with HE. (HA has some sort of support for these toys.)
I am already using few BLE Beacons as Home Arrival Sensors (ESPesence + HA + HE).
This works very well but the problem with BLE Beacons is luck of security.
I.e. BLE Beacon could be easily cloned even without physical access to the device.
Furthermore, BLE Beacons do not require even pairing.
Shelly BLE Button 1 claim it supports encryption and pairing.
If this is true statements these Shelly BLE Buttons could be used as a very nice Arrival Sensors.
Let me see what I will be able to achieve with these new toys.

2 Likes

It is actually should be possible to use BLU series sensor directly. Well.. kinda:

Shelly Plus/Pro devices can act as a gateway. Shelly script plays a role of mini-driver. There are some script samples on the topic directly in the Shelly script repository. The only thing that needs to be adjusted is to send event from shelly "gateway" to hub (basically sample scripts already do it; just too much info that can be cut down to lets say "present/not present").

Websocket based custom driver has support for script interoperability, For each preallocated script slot it spawns a "switch" style child device (reflecting and controlling if scripts are running or not) with "variable" attribute. That attribute reflects last event sent from related shelly script (slot). RM may interpret it as needed.

So in the end if you have any Shelly Plus/Pro device you can use Shelly BLU without extra addons.
(encription support - is a topic to investigate)

I do have one Shelly I4 already installed. Your idea is interesting and I may try to figure out how
to implement something. Unfortunately I not very fluent with SW (all these scripts, etc.)
but I may try this.

This is a script from shelly repository

/**
 * This script will use BLE observer to listen for advertising data from nearby Shelly BLU devices,
 * decodes the data using a BTHome data structure, and emits the decoded data for further processing.
 *
 * This script DOESN'T execute actions, only emit events. Can be used with `ble-events-handler.js` example.
 * You can configure the event name, by default its `shelly-blu`, the body of the event contains all the data
 * parsed from the BLE device
 *
 * All paramerts that the BLU devices can return:
 * Each device will provide data solely from its sensors.
 * - pid - packet ID
 * - battery - the battery level of the device in %
 * - temperature - the temperature value in °C if the device has temperature sensor
 * - humidity - the himidity value in % if the device has humidity sensor
 * - illuminance - the illuminance value in lux if the device has light sensor
 * - motion - 0/1 (motion/clear) if the device has motion sensor
 * - window - 0/1 (close/open) if the device has reed switch
 * - button - the number of presses if the device has button
 * - rotation - the angle of rotatation in ° if the device has gyroscope
 * - rssi - the signal strength is dB
 * - address - The mac address of the Shelly BLU device
 * 
 * Example event data: {"component":"script:*","name":"script","id":*,"now":*,"info":{"component":"script:*","id":*,"event":"shelly-blu","data":{"encryption":false,"BTHome_version":2,"pid":118,"battery":100,"button":1,"rssi":-76,"address":*},"ts":*}}
 */

/******************* START CHANGE HERE *******************/
let CONFIG = {
  // Specify the destination event where the decoded BLE data will be emitted. It allows for easy identification by other applications/scripts
  eventName: "shelly-blu",

  // When set to true, debug messages will be logged to the console
  debug: false,

  //When set to true and the script ownes the scanner, the scan will be active. 
  //Active scan means the scanner will ping back the Bluetooth device to receive all its data, but it will drain the battery faster
  active: false,
};
/******************* STOP CHANGE HERE *******************/

let ALLTERCO_MFD_ID_STR = "0ba9";
let BTHOME_SVC_ID_STR = "fcd2";

let uint8 = 0;
let int8 = 1;
let uint16 = 2;
let int16 = 3;
let uint24 = 4;
let int24 = 5;

//Logs the provided message with an optional prefix to the console.
function logger(message, prefix) {
  //exit if the debug isn't enabled
  if (!CONFIG.debug) {
    return;
  }

  let finalText = "";

  //if the message is list loop over it
  if (Array.isArray(message)) {
    for (let i = 0; i < message.length; i++) {
      finalText = finalText + " " + JSON.stringify(message[i]);
    }
  } else {
    finalText = JSON.stringify(message);
  }

  //the prefix must be string
  if (typeof prefix !== "string") {
    prefix = "";
  } else {
    prefix = prefix + ":";
  }

  //log the result
  console.log(prefix, finalText);
}

// The BTH object defines the structure of the BTHome data
let BTH = {};
BTH[0x00] = { n: "pid", t: uint8 };
BTH[0x01] = { n: "battery", t: uint8, u: "%" };
BTH[0x02] = { n: "temperature", t: int16, f: 0.01, u: "tC" };
BTH[0x03] = { n: "humidity", t: uint16, f: 0.01, u: "%" };
BTH[0x05] = { n: "illuminance", t: uint24, f: 0.01 };
BTH[0x21] = { n: "motion", t: uint8 };
BTH[0x2d] = { n: "window", t: uint8 };
BTH[0x3a] = { n: "button", t: uint8 };
BTH[0x3f] = { n: "rotation", t: int16, f: 0.1 };

function getByteSize(type) {
  if (type === uint8 || type === int8) return 1;
  if (type === uint16 || type === int16) return 2;
  if (type === uint24 || type === int24) return 3;
  //impossible as advertisements are much smaller;
  return 255;
}

// functions for decoding and unpacking the service data from Shelly BLU devices
let BTHomeDecoder = {
  utoi: function (num, bitsz) {
    let mask = 1 << (bitsz - 1);
    return num & mask ? num - (1 << bitsz) : num;
  },
  getUInt8: function (buffer) {
    return buffer.at(0);
  },
  getInt8: function (buffer) {
    return this.utoi(this.getUInt8(buffer), 8);
  },
  getUInt16LE: function (buffer) {
    return 0xffff & ((buffer.at(1) << 8) | buffer.at(0));
  },
  getInt16LE: function (buffer) {
    return this.utoi(this.getUInt16LE(buffer), 16);
  },
  getUInt24LE: function (buffer) {
    return (
      0x00ffffff & ((buffer.at(2) << 16) | (buffer.at(1) << 8) | buffer.at(0))
    );
  },
  getInt24LE: function (buffer) {
    return this.utoi(this.getUInt24LE(buffer), 24);
  },
  getBufValue: function (type, buffer) {
    if (buffer.length < getByteSize(type)) return null;
    let res = null;
    if (type === uint8) res = this.getUInt8(buffer);
    if (type === int8) res = this.getInt8(buffer);
    if (type === uint16) res = this.getUInt16LE(buffer);
    if (type === int16) res = this.getInt16LE(buffer);
    if (type === uint24) res = this.getUInt24LE(buffer);
    if (type === int24) res = this.getInt24LE(buffer);
    return res;
  },

  // Unpacks the service data buffer from a Shelly BLU device
  unpack: function (buffer) {
    //beacons might not provide BTH service data
    if (typeof buffer !== "string" || buffer.length === 0) return null;
    let result = {};
    let _dib = buffer.at(0);
    result["encryption"] = _dib & 0x1 ? true : false;
    result["BTHome_version"] = _dib >> 5;
    if (result["BTHome_version"] !== 2) return null;
    //can not handle encrypted data
    if (result["encryption"]) return result;
    buffer = buffer.slice(1);

    let _bth;
    let _value;
    while (buffer.length > 0) {
      _bth = BTH[buffer.at(0)];
      if (typeof _bth === "undefined") {
        logger("unknown type", "BTH");
        break;
      }
      buffer = buffer.slice(1);
      _value = this.getBufValue(_bth.t, buffer);
      if (_value === null) break;
      if (typeof _bth.f !== "undefined") _value = _value * _bth.f;
      result[_bth.n] = _value;
      buffer = buffer.slice(getByteSize(_bth.t));
    }
    return result;
  },
};

// Еmitting the decoded BLE data to a specified event. It allows other scripts to receive and process the emitted data
function emitData(data) {
  if (typeof data !== "object") {
    return;
  }

  Shelly.emitEvent(CONFIG.eventName, data);
}

//saving the id of the last packet, this is used to filter the duplicated packets
let lastPacketId = 0x100;

// Callback for the BLE scanner object
function BLEScanCallback(event, result) {
  //exit if not a result of a scan
  if (event !== BLE.Scanner.SCAN_RESULT) {
    return;
  }

  //exit if service_data member is missing
  if (
    typeof result.service_data === "undefined" ||
    typeof result.service_data[BTHOME_SVC_ID_STR] === "undefined"
  ) {
    logger("Missing service_data member", "Error");
    return;
  }

  let unpackedData = BTHomeDecoder.unpack(
    result.service_data[BTHOME_SVC_ID_STR]
  );

  //exit if unpacked data is null or the device is encrypted
  if (
    unpackedData === null ||
    typeof unpackedData === "undefined" ||
    unpackedData["encryption"]
  ) {
    logger("Encrypted devices are not supported", "Error");
    return;
  }

  //exit if the event is duplicated
  if (lastPacketId === unpackedData.pid) {
    return;
  }

  lastPacketId = unpackedData.pid;

  unpackedData.rssi = result.rssi;
  unpackedData.address = result.addr;

  emitData(unpackedData);
}

// Initializes the script and performs the necessary checks and configurations
function init() {
  //exit if can't find the config
  if (typeof CONFIG === "undefined") {
    console.log("Error: Undefined config");
    return;
  }

  //get the config of ble component
  let BLEConfig = Shelly.getComponentConfig("ble");

  //exit if the BLE isn't enabled
  if (!BLEConfig.enable) {
    console.log(
      "Error: The Bluetooth is not enabled, please enable it from settings"
    );
    die();
    return;
  }

  //check if the scanner is already running
  if (BLE.Scanner.isRunning()) {
    console.log("Info: The BLE gateway is running, the BLE scan configuration is managed by the device");
  }
  else {
    //start the scanner
    let bleScanner = BLE.Scanner.Start({
        duration_ms: BLE.Scanner.INFINITE_SCAN,
        active: CONFIG.active
    });

    if(!bleScanner) {
      console.log("Error: Can not start new scanner");
      die();
    }
  }

  //subscribe a callback to BLE scanner
  BLE.Scanner.Subscribe(BLEScanCallback);
}

init();

The line

  • Shelly.emitEvent(CONFIG.eventName, data);

sends all the gathered data. I assume if to change it to

  • Shelly.emitEvent(CONFIG.eventName, data.window); // window - 0/1 (close/open) if the device has reed switch

it will send only null/open/closed state (for door/window sensor). Or

  • Shelly.emitEvent(CONFIG.eventName, data.button); // button - the number of presses if the device has button

for BLU Button 1

Shelly broadcasts event to all registered listeners around. And open websocket (by the hub driver) becomes one of them.

Thank you for quick response and idea. I will try this a bit later.

Hi Guys,

did anyone got hubitat running in any way with Shelly BLE devices?

On my side I have plans to work on such support in my driver. Yet I'm still waiting for such device to put my hands on)
Shelly Device Handlers for Hubitat - :gear: Custom Apps and Drivers / Custom Drivers - Hubitat

1 Like

Hi! I've been working on an official line of Hubitat drivers for Shelly devices, here: [RELEASE] Shelly Device Drivers (NO MQTT NEEDED)

I just added support for Shelly Motion Blu, Shelly Door/Window Blu, and Shelly Button 1 Blu devices.

There's a driver for the Shelly BLE Gateway, and the driver I've got for the Shelly Plug US also supports acting as a gateway. As I work through the Shelly devices and add more drivers, I'll allow any device that has Bluetooth Gateway capabilities (in the hardware) to act as a Bluetooth Gateway for Hubitat.

The Button 1 Blu driver has support for single, double, and triple press, as well as held, and it has presence functionality too, since it reports in every minute or two if you have the 'beacon' option turned on for it (via the Shelly Mobile app).

1 Like

Does your driver support encryption for the button 1?

And as a general question to all - what's the battery life like?

Nope. It's just using the reference script from Shelly, which doesn't support encryption.

Hard to say... the battery stats reported from the button are all over the place. One press it'll be 52% then the next press is 85%... Nothing my drivers are doing on that, it's just what the Button 1 spits out. I've never really cared about battery percentage reports on IoT stuff, it's almost always useless. I'll let you know when I have to swap out the battery, but that'll probably be a year or two, if it's anything like the battery life I got on a similar device (Tile Mate) when I was still using that (prior to the buyout by Life360).

Thanks. The only true test is when the battery dies, hence I log all my battery changes and they tend to be consistent if the same brand battery is used.

A year is pretty good IMO. My current non commercial BLE beacons need a battery change around 6-8 months. They are sending out signals every few seconds.

The Button 1, with beacon option enabled, reports "button 0 pressed" every 25 seconds or so. I've seen it go nearly 3 minutes before, so not sure what's up with that, but it's definitely way less often than most 'pure beacon' devices. So unless the electronics in it are much less efficient it should last more than 6-8 months in beacon mode since it's sending a lot less often.

That's how I treat every device in my house (other than high end things like phones, tablets, laptops). I just use them until they die. Especially if they have rechargeable batteries. Battery percentages on most devices are based solely on voltage. Lower voltage = less energy left. Then the firmware correlates X voltage = Y percent and reports that.

That works fine if you're always using the same batteries as the firmware was calibrated for. Alkaline are around 1v55 for a fully charged "lithium" alkaline and around 1v5 for normal alkaline. They're dead around 1v1 to 1v and have a pretty straight discharge slope. "50% remaining" is around 1v2.

NiMH start out at 1v4 or so, fully charged. They have a practically flat discharge curve that stays around 1v2 for about 80% of the total charge life, then just drops off to dead in a matter of no time. If you put a freshly charged NiMH in a device with firmware expecting alkaline it'll report like 70% charge the day you put it in, then quickly drop to around 50% and stay around 50% for the entire time you're using it, then in a matter of a couple days it'll go from 50% to dead.

Generic graph showing very generic discharge curves. But you can see how 25% remaining on an alkaline is like hours away from totally dead on NiMh. Also the blue line here needs to start about 1 square lower, since NiMh are about 0v1 less voltage than alkaline at the start.

But yeah, battery level percentages are pretty useless overall, and doubly so if you toss NiMH cells in something expecting alkaline.

1 Like

Would it be possible for the shellys that can act as a gateway to detect the bluetooth of a mobile phone for a reliable arrival action?

Greetings