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":[]}]