Shelly Custom Driver

@dmitry.rozovik

For a while I was using successfully Shelly Plus/Pro xPM for the Shelly I4 sensor device. I am not sure when problem popped up but now Shelly Driver generates a lot of warnings and errors:

[dev:2457](http://192.168.20.91/logs#)2024-06-30 11:23:02.603 AM[warn](http://192.168.20.91/logs#)2024-06-30T11:23:02.860-04:00[US/Eastern]: Unconfigured (1) script instance event [component:script:1, data:[2, [[3a:5d:17:4e:42:db, -70, , Hhbz/koXI1dEMVkRMg47iB0Z9uN/4lOzNhuLW3znmg==], [67:cb:35:31:2e:21, -87, AgEaDf9MABYIAMOZx23Rn20=, ], [d6:35:86:cd:ca:de, -67, AgEGDv9pCdY1hs3K3h4bACEE, CRY9/WPArAAhBA==]]], id:1, event:ble.scan_result, ts:1719760982.86]

[dev:2457](http://192.168.20.91/logs#)2024-06-30 11:23:02.295 AM[warn](http://192.168.20.91/logs#)2024-06-30T11:23:02.550-04:00[US/Eastern]: Unconfigured (1) script instance event [component:script:1, data:[2, [[4c:e1:bf:b8:fd:db, -92, AgEaDf9MABYIAHrpJlThq74=, ], [a8:80:55:b8:71:eb, -92, AgEGAwIBohQWAaIBl8rRPl9/jIlFQdtt+hNxOQ==, AwlUWRn/0AeJAwAADACtpQjSipit2QprSaB9JSIi], [00:1c:c2:78:c1:7e, -83, AgEGDAlEUkVPdGYwN3c3RAMDSFMDGQXABv9IRgAAAw==, ]]], id:1, event:ble.scan_result, ts:1719760982.55]

[dev:2457](http://192.168.20.91/logs#)2024-06-30 11:23:02.042 AM[warn](http://192.168.20.91/logs#)2024-06-30T11:23:02.240-04:00[US/Eastern]: Unconfigured (1) script instance event [component:script:1, data:[2, [[c1:c1:10:e5:c8:44, -98, B/9MABICAAI=, ], [38:68:a4:ac:ca:25, -88, G/91AEIEAYBmOGikrMolOmikrMokAfwAAAAAAA==, ], [ee:49:df:15:06:42, -78, EQlGUjpSMjA6U04wMTQ3ICAgAxkAAAIBBgIK9A==, AwMjFREHI9G86l94IxXe7xISIxUAAA==], [eb:12:9e:e5:e2:86, -88, B/9MABICAAI=, ], [d8:e0:e1:0c:78:1f, -76, AgEaG/91AEIEAQFv2ODhDHgf2uDhDHgemQAAAAAAAA==, GwhbVFZdIFNhbXN1bmcgOCBTZXJpZXMgKDY1KQ==], [d8:e0:e1:0c:78:1f, -88, AgEaG/91AEIEASBvFwcAAAAAAAAAAAAAAAAAAAAAAA==, GwhbVFZdIFNhbXN1bmcgOCBTZXJpZXMgKDY1KQ==], [c0:03:46:37:41:ee, -71, AgEGEP9pCcADRjdB7v8JABEEAVU=, ]]], id:1, event:ble.scan_result, ts:1719760982.24]

[dev:2457](http://192.168.20.91/logs#)2024-06-30 11:23:01.667 AM[warn](http://192.168.20.91/logs#)2024-06-30T11:23:01.930-04:00[US/Eastern]: Unconfigured (1) script instance event [component:script:1, data:[2, [[e3:25:90:54:f7:be, -82, B/9MABICAAI=, ], [4a:f9:ff:7b:d2:b4, -84, AgEaDf9MABYIAPvyn6EPuWw=, ], [2a:ca:6d:f8:7c:66, -92, Hv8GAAEJICISBpblRF6kxYSns+M3Lv06p6CkMAsU/A==, ], [d8:e0:e1:0c:78:1f, -85, AgEaG/91AEIEAQFv2ODhDHgf2uDhDHgemQAAAAAAAA==, ]]], id:1, event:ble.scan_result, ts:1719760981.93]

[dev:2457](http://192.168.20.91/logs#)2024-06-30 11:23:01.326 AM[warn](http://192.168.20.91/logs#)2024-06-30T11:23:01.590-04:00[US/Eastern]: Unconfigured (1) script instance event [component:script:1, data:[2, [[d3:43:00:bf:77:58, -65, AgEGEP9pCdNDAL93WAILABIAAVY=, CRY9/XvAVgASAA==], [79:67:31:69:35:52, -91, AgEaAgoMC/9MABAGRx2hMvF4, ], [c7:ca:3d:0b:77:c8, -69, B/9MABICAAA=, ], [10:2d:41:02:dd:40, -94, AgEGD/+PAwoQQgwBP90CQS0Q7A==, ]]], id:1, event:ble.scan_result, ts:1719760981.59]

[dev:2457](http://192.168.20.91/logs#)2024-06-30 11:23:00.994 AM[warn](http://192.168.20.91/logs#)2024-06-30T11:23:01.260-04:00[US/Eastern]: Unconfigured (1) script instance event [component:script:1, data:[2, [[a4:d1:8c:6f:07:d3, -80, AgEaC/9MAAkGAwAAAAAA, ], [c7:85:6c:ec:fc:5c, -65, AgEGCf9ZAMeFbOz8XA==, ]]], id:1, event:ble.scan_result, ts:1719760981.26]

[dev:2457](http://192.168.20.91/logs#)2024-06-30 11:23:00.730 AM[warn](http://192.168.20.91/logs#)2024-06-30T11:23:00.950-04:00[US/Eastern]: Unconfigured (1) script instance event [component:script:1, data:[2, [[2d:19:b7:cf:20:77, -83, AgEGGv9MAAIVJobznLraRliFSqYufl6LjQABAAAL, ], [66:75:cc:ca:80:17, -83, AgEaAgoIB/9MABACIQA=, ], [d0:2e:ab:66:d3:40, -70, AgEGBQLA/wByDP8NAH8GAOECAwD/wQ==, DQlVLUJvbHQtWldhdmUFEggACAACCgA=], [eb:f4:76:ee:26:ff, -71, AgEGEP9pCev0du4m/wILABEAAVg=, CRY9/XvAWAARAA==]]], id:1, event:ble.scan_result, ts:1719760980.95]

[dev:2457](http://192.168.20.91/logs#)2024-06-30 11:23:00.372 AM[warn](http://192.168.20.91/logs#)2024-06-30T11:23:00.630-04:00[US/Eastern]: Unconfigured (1) script instance event [component:script:1, data:[2, [[c7:85:6c:ec:fc:5c, -62, AgEGCf9ZAMeFbOz8XA==, EQcbxdWlAgC4n+YRTSIADaLLBhYADUgQ0Q==], [57:2b:cc:ca:28:26, -84, AgEaAgoMC/9MABAGAR3a08UY, ]]], id:1, event:ble.scan_result, ts:1719760980.63]

[dev:2457](http://192.168.20.91/logs#)2024-06-30 11:22:27.811 AM[error](http://192.168.20.91/logs#)com.hubitat.app.exception.UnknownDeviceTypeException: Device type 'Generic Component Script' in namespace 'drozovyk' not found on line 500 (method updated)

I tried to fresh install/reinstall this custom driver but unfortunately this did not fix a problem.
As a result not I stopped using this device and driver.

But it will be nice and helpful if this problem could be fixed.

Oh..
It basically says that an event from script instance is nowhere to forward to

Just run "Configure" to spawn child device.

"Unconfigured (1) script instance event" - script from slot 1 sent an event. But there is no child script device to accept this event.

Device type 'Generic Component Script' in namespace 'drozovyk' not found on line 500 (method updated)

This one means it can't find the driver for the child device to install. Did you installed the whole bundle, or file separately?

All 4 child devices are present:
image

Clicking on Configure generates these errors:

No I did not install the entire bundle because I am using only Shelly I4 sensor device. I really don't want to install things which will not be used.
I guess, I installed all required libraries:

And only Shelly driver:
image

Did I miss something?

Yes. Due to scipt it wants to install "Script child device"

This one
ge4d / hubitat-code / Drivers / Virtual / drozovyk.GenericComponentScript.groovy — Bitbucket

Assuming it worked fine before, you have installed "Input event device" for inputs
ge4d / hubitat-code / Drivers / Virtual / drozovyk.GenericComponentInputEvent.groovy — Bitbucket

Script child device accepts script events so you can handle them from RM

In your case two child devices are needed.

One to handle input channels and one to handle scripts.

Script child device has a switch interface that equals to starting/stopping corresponding script

And printing in JS script these lines sends events to one of three attributes

// variable (visible as a dashboard tile)
Shelly.emitEvent("variable", myData);

// customNumericAttribute
Shelly.emitEvent("number", myData);

// customTextAttribute
Shelly.emitEvent("string", myData);
Shelly.emitEvent("text", myData);

yes, this was installed and just in case I refreshed it with a latest version.

I am not sure what for I may need that script child device but now everything looks OK.

Thank you very much for the very quick response and assistance for the fixing problem(s).

For example you have a script that handles BT devices. You can send some BT data to RM to trigger some rules (like specific BT sensor/button reading).
And you can start/stop this script from dashboard if for some reason you want to stop handling BT devices.

Futhermore you can use some other shelly scripts. It's not a good example, but.. Shelly have weather script example. And you can make your I4 to send you some weather events (like some distributed sub-system/hub)

Very interesting.
As we speak I am using 3 Shelly BT Buttons. But they are integrated with HE via Home Assistant. Are you saying, I can bypass HA? This will be very nice. Unfortunately my SW skills is somewhat one way (i.e. I can understand the existing code but writing something is not easy). Could you please assist in moving BT Shelly Buttons (3 of them) from HA to the HE?

Yes. This is possible. But will require some tinkering. I have plans to write a companion Hubitat application to manage BT gateway mode for shelly devices. Yet I have difficulties getting needed devices to play with. And some timing problems due to war (((

Here is one of events casued by your BT devices (from your logs)

[
component:script:1,
data:[2,
[
[2d:19:b7:cf:20:77, -83, AgEGGv9MAAIVJobznLraRliFSqYufl6LjQABAAAL, ],
[66:75:cc:ca:80:17, -83, AgEaAgoIB/9MABACIQA=, ],
[d0:2e:ab:66:d3:40, -70, AgEGBQLA/wByDP8NAH8GAOECAwD/wQ==, DQlVLUJvbHQtWldhdmUFEggACAACCgA=],
[eb:f4:76:ee:26:ff, -71, AgEGEP9pCev0du4m/wILABEAAVg=, CRY9/XvAWAARAA==]
]
], id:1, event:ble.scan_result, ts:1719760980.95]

It reports 4 devices and their binary data.
Script can be modified (or new script added) to send specific event when specific BT device sends specific data.
The complexity atm is that device list (their ids) need to be handled by hand each time new device is added (this is why I want to make an app; and also to support migration - so the same button can be used within range of different gateway devices transparently).

1 Like

This is a modified Shelly script I shared before in one private mail thread

/**
 * 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
 *
 * Represents data provided by each device.
 * Every value illustrating a sensor reading (e.g., button) may be a singular sensor value or 
 * an array of values if the object has multiple instances. 
 * 
 * @typedef {Object} DeviceData
 * @property {number} pid - Packet ID.
 * @property {number} battery - The battery level of the device in percentage (%).
 * @property {number} rssi - The signal strength in decibels (dB).
 * @property {string} address - The MAC address of the Shelly BLU device.
 * @property {string} model - The model of the Shelly BLU device.
 * @property {number | number[]} [temperature] - The temperature value in degrees Celsius if the device has a temperature sensor. (Can be an array if has multiple instances)
 * @property {number | number[]} [humidity] - The humidity value in percentage (%) if the device has a humidity sensor. (Can be an array if has multiple instances)
 * @property {number | number[]} [illuminance] - The illuminance value in lux if the device has a light sensor. (Can be an array if has multiple instances)
 * @property {number | number[]} [motion] - Motion status: 0 for clear, 1 for motion (if the device has a motion sensor). (Can be an array if has multiple instances)
 * @property {number | number[]} [window] - Window status: 0 for closed, 1 for open (if the device has a reed switch). (Can be an array if has multiple instances)
 * @property {number | number[]} [button] - The number of presses if the device has a button. (Can be an array if has multiple instances)
 * @property {number | number[]} [rotation] - The angle of rotation in degrees if the device has a gyroscope. (Can be an array if has multiple instances)
 * 
 * @example
 * {"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 *******************/
const 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",

  // If the script owns the scanner and this value is set to true, the scan will be active.
  // If the script does not own the scanner, it may remain passive even when set to true. 
  // 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,

  // When set to true, debug messages will be logged to the console
  debug: false,
};
/******************* STOP CHANGE HERE *******************/

const BTHOME_SVC_ID_STR = "fcd2";

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

// The BTH object defines the structure of the BTHome data
const BTH = {
  0x00: { n: "pid", t: uint8 },
  0x01: { n: "battery", t: uint8, u: "%" },
  0x02: { n: "temperature", t: int16, f: 0.01, u: "tC" },
  0x03: { n: "humidity", t: uint16, f: 0.01, u: "%" },
  0x05: { n: "illuminance", t: uint24, f: 0.01 },
  0x21: { n: "motion", t: uint8 },
  0x2d: { n: "window", t: uint8 },
  0x3a: { n: "button", t: uint8 },
  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
const BTHomeDecoder = {
  utoi: function (num, bitsz) {
    const 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") {
        console.log("BTH: Unknown type");
        break;
      }
      buffer = buffer.slice(1);
      _value = this.getBufValue(_bth.t, buffer);
      if (_value === null) {
          break;
      }
      
      if (typeof _bth.f !== "undefined") {
          _value = _value * _bth.f;
      }

      if (typeof result[_bth.n] === "undefined") {
        result[_bth.n] = _value;
      }
      else {
        if (Array.isArray(result[_bth.n])) {
          result[_bth.n].push(_value);
        } 
        else {
          result[_bth.n] = [ 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
 * @param {DeviceData} data 
 */
function emitData(data) {
  if (typeof data !== "object") {
    return;
  }

  // Dmytro: This is the original event sending function
  // it takes event name from the config at the beginning of the script
  // And by default it is set to "shelly-blu". Not what we want.
  
  // Data format should look like This
  // {"encryption":false,"BTHome_version":2,"pid":118,"battery":100,"button":1,"rssi":-76,"address":*}
  
  // Shelly.emitEvent(CONFIG.eventName, data); 
  
  // Instead we will call these:
  
  // For debug purposes we will send entire data object to be able to inspect what were reported and decoded
  Shelly.emitEvent("text", data);

  // Here we gonna send door/window sensor
  // Cooment at the beginnig says:
  // * @property {number | number[]} [window] - Window status: 0 for closed, 1 for open (if the device has a reed switch). (Can be an array if has multiple instances)
  // It means we are interested in "window" property.
  // Let's assume for now that it is a single instance (not array)
  // First of all let's see if this property is present/reported
  if(typeof data["window"] !== "undefined") {
    // Now we will convert it to text values
    Shelly.emitEvent("variable", (data.window > 0) ? "open" : "closed");
  }
  
  // Number is unused and kept for convinience
  //Shelly.emitEvent("number", eventCount);
}

//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") {
    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"]) {
    console.log("Error: Encrypted devices are not supported");
    return;
  }

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

  lastPacketId = unpackedData.pid;

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

  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
  const 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"
    );
    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
    const bleScanner = BLE.Scanner.Start({
        duration_ms: BLE.Scanner.INFINITE_SCAN,
        active: CONFIG.active
    });

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

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

  // disable console.log when logs are disabled
  if (!CONFIG.debug) {
    console.log = function() {};
  }
}

init();

It sends event triggered by BT window sensor. But without filtering out of the specific one.

  // Here we gonna send door/window sensor
  // Cooment at the beginnig says:
  // * @property {number | number[]} [window] - Window status: 0 for closed, 1 for open (if the device has a reed switch). (Can be an array if has multiple instances)
  // It means we are interested in "window" property.
  // Let's assume for now that it is a single instance (not array)
  // First of all let's see if this property is present/reported
  if(typeof data["window"] !== "undefined") {
    // Now we will convert it to text values
    Shelly.emitEvent("variable", (data.window > 0) ? "open" : "closed");
  }

Oh yes, this is terrible thing.

Handling a BT Devices sounds like a very bright idea.
Unfortunately my SW skills is not enough for playing with all these scripts myself.
I will wait for yours app but I certainly can help you with testing and debugging.
As of now I have one Shelly I4 Sensor (2 inputs are used and 2 are spare) and 3 Shelly BT Buttons (all 3 are used but currently integrated with HE via HA).
Please let me know If you want me to test something but i will need a clear instructions what exactly should I do.

Thank you very much for shearing this script.
I will need some (sizable) time to understand what this script is doing and how to modify it for handling just a BT Button(s). This will good start for the beginning.

And I assume, this script(s) must be installed on a Shelly Device itself. Am I right?

Yes. It's a mJS script to run on Shelly devices

Btw, forgot to mention:

You can rename child devices (both name and label) at your taste. Child devices are identified by internal "network id" that is composed from root device network id and child device suffix. Name and label are not used for addressing internally.

Thank you very much for the ideas and tips.

I am struggling with a Shelly Plus 1 with the add on for temperature sensors. I seem to be able to get the basic switch function working but I am unable to get the two temperature probes to register. They are all working successfully on the Shelly app. I have tried several drivers but do not see any child devices after trying configure.

The temperature showing is wrong and hasn’t changed for several weeks

Could you show logs and the bottom part of the driver page with data attributes? There should be a component list to check what driver has received from your device.

Logs might contain some errors. Debug logs are preferable to trace entire data path in case something is messing.

I have changed to the Shelly Plus/Pro xPM driver loaded using the bundle.
Here are the logs I collected

Is it the latest version?
I know this issue. It should be fixed. It is related to the incorrect type casting.
The fix were to use explicit .toString() function of the map that goes into logDebug(..) call.

Could you share the link you used (to check if it points the latest version)?

[latest vrsion also got voltage sensor support that is used by add-on module]
All of these should be now supported

Peripheral type Component type
ds18b20 temperature
dht22 temperature, humidity
digital_in input
analog_in input
voltmeter voltmeter