Shelly blu DW sensor -> shelly plus plug s scripting -> node-red

I just got my new shelly blu DW sensor which is small and has low energy usage.
I managed to connect it to a shelly plus Plug s and it works ok. Now I want to create actions within the shelly script on the plug s in order to send the status of the window to node-red. The plug should be a relais station of 3 DW sensors.
I need help in doing this in the script.

I am already sending http messages (actions) from a shelly button to node red which works ok.
That is excatly what I want to achive in my script.

The problem is solved.
The shelly blu door window sensor is realy very good. It is small and due to ble very efficient. In order to use it in node-red you need to use a shelly plus or shelly pro and a script running on these shellies.
I am using http request to connect the shelly as a relay for the bluetooth sensor to node-red but there are scripts using mqtt.

Here the script:

/**
 * This script uses the BLE scan functionality in scripting
 * Selects Shelly BLU DoorWindow from the aired advertisements, decodes
 * the service data payload and toggles a relay on the device on
 * button push
 */

// Shelly BLU devices:
// SBBT - Shelly BLU Button
// SBDW - Shelly BLU DoorWindow

// sample Shelly DW service_data payload
// 0x40 0x00 0x4E 0x01 0x64 0x05 0x00 0x00 0x00 0x2D 0x01 0x3F 0x00 0x00

// First byte: BTHome device info, 0x40 - no encryption, BTHome v.2
// bit 0: “Encryption flag”
// bit 1-4: “Reserved for future use”
// bit 5-7: “BTHome Version”

// AD 0: PID, 0x00
// Value: 0x4E

// AD 1: Battery, 0x01
// Value, 100%

// AD 2: Illuminance, 0x05
// Value: 0

// AD 3: Window, 0x2D
// Value: true, open

// AD 4: Rotation, 0x3F
// Value: 0

// Device name can be obtained if an active scan is performed
// You can rely only on the addresss filtering and forego device name matching

// CHANGE HERE


//Shelly Call Error Check
//HTTP Methode Error Check

function HTTP_ErrorCheck(response, extra) {
        if (response.code !== 200) {
            print('Error: Http call');
            print('Response: ', JSON.stringify(response));
            print('eMsg: ', response.message);
        }
        if (typeof (extra) !== 'undefined') {
            if (typeof (extra) === 'function') extra(response);
            if (typeof (extra) === 'object') extra.CallBack(response, extra.data);
        }
}

function Call_ErrorCheck(response, shellyCall_error_code, shellyCall_error_message, extra) {
        if (shellyCall_error_code !== 0) {
            print('Error: Shelly.call()');
            print('Response: ', JSON.stringify(response));
            print('eMsg: ', shellyCall_error_message);
            print('Response type is: ', typeof (response));
            if (typeof (response) !== 'object') {
                response = { callError: true, orgResponse: response };
            } else {
                response.callError = true;
            }
        }
        if (typeof (response) !== 'object') {
            response = { changedToObj: true, orgResponse: response };
        }
        if (typeof (extra) !== 'undefined') {
            if (typeof (extra) === 'function') extra(response);
            if (typeof (extra) === 'object') extra.CallBack(response, extra.data);
        }
}

function printOpen() {
  print("Window is open, will toggle the output");
  Shelly.call("Switch.set", { id: 0, on: true });
  Shelly.call('HTTP.GET', {url:"http://10.0.0.50:1880/output?s=shelly_DW-UGBU_1:open", timeout: 30}, Call_ErrorCheck, HTTP_ErrorCheck);
}

function printClosed() {
  print("Window is closed, will toggle the output");
  Shelly.call("Switch.set", { id: 0, on: false });
  Shelly.call('HTTP.GET', {url:"http://10.0.0.50:1880/output?s=shelly_DW-UGBU_1:closed", timeout: 30}, Call_ErrorCheck, HTTP_ErrorCheck);
}

// remove name prefix to not filter by device name
// remove address to not filter by address

let CONFIG = {
  //shelly_blu_name_prefix: 'SBDW',
  //"BIND" to only this address
  shelly_blu_address: "60:ef:ab:42:d4:a1",
  actions: [
    {
      cond: {
        Window: 0,
      },
      action: printClosed,
    },
    {
      cond: {
        Window: 1,
      },
      action: printOpen,
    },
  ],
};
// END OF CHANGE

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

let ALLTERCO_MFD_ID = JSON.parse("0x" + ALLTERCO_MFD_ID_STR);
let BTHOME_SVC_ID = JSON.parse("0x" + BTHOME_SVC_ID_STR);

let SCAN_DURATION = BLE.Scanner.INFINITE_SCAN;
let ACTIVE_SCAN =
  typeof CONFIG.shelly_blu_name_prefix !== "undefined" &&
  CONFIG.shelly_blu_name_prefix !== null;

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

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;
}

let BTH = [];
BTH[0x00] = { n: "pid", t: uint8 };
BTH[0x01] = { n: "Battery", t: uint8, u: "%" };
BTH[0x05] = { n: "Illuminance", t: uint24, f: 0.01 };
BTH[0x1a] = { n: "Door", t: uint8 };
BTH[0x20] = { n: "Moisture", t: uint8 };
BTH[0x2d] = { n: "Window", t: uint8 };
BTH[0x3a] = { n: "Button", t: uint8 };
BTH[0x3f] = { n: "Rotation", t: int16, f: 0.1 };

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;
  },
  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 (_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;
      result[_bth.n] = _value;
      buffer = buffer.slice(getByteSize(_bth.t));
    }
    return result;
  },
};

let ShellyBLUParser = {
  getData: function (res) {
    let result = BTHomeDecoder.unpack(res.service_data[BTHOME_SVC_ID_STR]);
    result.addr = res.addr;
    result.rssi = res.rssi;
    return result;
  },
};

let last_packet_id = 0x100;
function scanCB(ev, res) {
  if (ev !== BLE.Scanner.SCAN_RESULT) return;
  // skip if there is no service_data member
  if (
    typeof res.service_data === "undefined" ||
    typeof res.service_data[BTHOME_SVC_ID_STR] === "undefined"
  )
    return;
  // skip if we are looking for name match but don't have active scan as we don't have name
  if (
    typeof CONFIG.shelly_blu_name_prefix !== "undefined" &&
    (typeof res.local_name === "undefined" ||
      res.local_name.indexOf(CONFIG.shelly_blu_name_prefix) !== 0)
  )
    return;
  // skip if we don't have address match
  if (
    typeof CONFIG.shelly_blu_address !== "undefined" &&
    CONFIG.shelly_blu_address !== res.addr
  )
    return;
  let BTHparsed = ShellyBLUParser.getData(res);
  // skip if parsing failed
  if (BTHparsed === null) {
    console.log("Failed to parse BTH data");
    return;
  }
  // skip, we are deduping results
  if (last_packet_id === BTHparsed.pid) return;
  last_packet_id = BTHparsed.pid;
  console.log("Shelly BTH packet: ", JSON.stringify(BTHparsed));
  // execute actions from CONFIG
  let aIdx = null;
  for (aIdx in CONFIG.actions) {
    // skip if no condition defined
    if (typeof CONFIG.actions[aIdx]["cond"] === "undefined") continue;
    let cond = CONFIG.actions[aIdx]["cond"];
    let cIdx = null;
    let run = true;
    for (cIdx in cond) {
      if (typeof BTHparsed[cIdx] === "undefined") run = false;
      if (BTHparsed[cIdx] !== cond[cIdx]) run = false;
    }
    // if all conditions evaluated to true then execute
    if (run) CONFIG.actions[aIdx]["action"](BTHparsed);
  }
}

BLE.Scanner.Start({ duration_ms: SCAN_DURATION, active: ACTIVE_SCAN }, scanCB);

and here my node-red nodes to receive the requests

[{"id":"bcf7383f.7d7018","type":"http in","z":"c9f02597841e11f7","name":"output","url":"/output","method":"get","upload":true,"swaggerDoc":"","x":70,"y":780,"wires":[["8146164e.545918"]]},{"id":"b2581bc3.ab74c8","type":"http response","z":"c9f02597841e11f7","name":"","statusCode":"200","headers":{},"x":400,"y":780,"wires":[]},{"id":"f0ea93fc.9a846","type":"html","z":"c9f02597841e11f7","name":"","property":"payload","outproperty":"payload","tag":"body","ret":"html","as":"single","x":150,"y":840,"wires":[["a55076ae.e77138"]]},{"id":"a55076ae.e77138","type":"function","z":"c9f02597841e11f7","name":"","func":"msg.payload = msg.payload[0];\nreturn msg;","outputs":1,"noerr":0,"x":280,"y":840,"wires":[["da992246.5bfa6"]]},{"id":"8146164e.545918","type":"template","z":"c9f02597841e11f7","name":"shelly page","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<html>\n    <head></head>\n    <body>\n        {{req.query.s}}\n    </body>\n</html>","x":210,"y":780,"wires":[["f0ea93fc.9a846","b2581bc3.ab74c8"]]},{"id":"da992246.5bfa6","type":"switch","z":"c9f02597841e11f7","name":"","property":"payload","propertyType":"msg","rules":[{"t":"cont","v":"shelly_Button1","vt":"str"},{"t":"cont","v":"shelly_Button2","vt":"str"},{"t":"cont","v":"shelly_1pm191","vt":"str"},{"t":"cont","v":"shelly_DW-UGBU_1","vt":"str"}],"checkall":"true","repair":false,"outputs":4,"x":410,"y":840,"wires":[["800d2d48.b6b1d"],["4137125b8bfed28b"],["de0eae1240f50b8c"],["d68841ace2e51093"]]},{"id":"800d2d48.b6b1d","type":"switch","z":"c9f02597841e11f7","name":"shelly_Button1","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"shelly_Button1:s","vt":"str"},{"t":"eq","v":"shelly_Button1:ss","vt":"str"},{"t":"eq","v":"shelly_Button1:sss","vt":"str"},{"t":"eq","v":"shelly_Button1:l","vt":"str"}],"checkall":"true","repair":false,"outputs":4,"x":640,"y":700,"wires":[["ce3ce537025552e1"],["1e1c8115.3ff44f"],["effaadcb.c1951"],["d03caf375de7919d"]]},{"id":"4137125b8bfed28b","type":"switch","z":"c9f02597841e11f7","name":"shelly_Button2","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"shelly_Button2:s","vt":"str"},{"t":"eq","v":"shelly_Button2:ss","vt":"str"},{"t":"eq","v":"shelly_Button2:sss","vt":"str"},{"t":"eq","v":"shelly_Button2:l","vt":"str"}],"checkall":"true","repair":false,"outputs":4,"x":640,"y":1000,"wires":[["dfed5223dffde3ee"],["bfe2254927718aa3"],["3b45a66eafe26824"],["93c14f2e4800c612"]]},{"id":"de0eae1240f50b8c","type":"switch","z":"c9f02597841e11f7","name":"shelly_1pm191:on/off","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"shelly_1pm191:on","vt":"str"},{"t":"eq","v":"shelly_1pm191:off","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":660,"y":1160,"wires":[["5deae5fa4c7bf36f"],["3ba2d90c80e47596"]]},{"id":"d68841ace2e51093","type":"debug","z":"c9f02597841e11f7","name":"debug 7","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":580,"y":1260,"wires":[]}]

sorry for the last post. How can I delete it?

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.