ALLTERCO / shelly-script-examples

Shelly Scripts for Gen2 Shelly devices
Apache License 2.0
200 stars 55 forks source link

BLU direct controll through bluetooth #111

Open freggl8 opened 3 months ago

freggl8 commented 3 months ago

I use it in my car to disconnect the battery!

Perhsps you can use the script. I made some changes but can't remember who was the original creator but it works great (Bluetooth-Gateway has to be deactivated):

to pair a BLU-device you press the button after connecting power (time to pair is 10 sec):

// ----------------------------------------------------------------------- // Developed by YBC // // Date: 01/06/2022 // Version: 1.2 // // Shelly Script: This script allows registering up to 45x BLU Button1 to use autonomous open door.   // When you push the registered BLU Button1, the relay turns on and turns off in 1 second. // // DEVICES:  // - Shelly PLUS 1 (to use bluetooth gateway and run the script), // - Shelly BLU Button 1 (to send push) // // INSTRUCTIONS // Register BLU Button1 // When you plug your Shelly Plus, you have 10 seconds to register and unregister. // // - Push one time BLU Button1 within 10s to register the button // - Push twice BLU Button1 within 10s to unregister the button // // www.shellycanarias.com // -----------------------------------------------------------------------

let BTHparsedd = null; let local_name = null; let buttons_cache = []; let ALLTERCO_DEVICE_NAME_PREFIX = "SBBT"; let BTHOME_SVC_ID_STR = "fcd2"; let SCAN_DURATION = BLE.Scanner.INFINITE_SCAN; let CONFIG_DURATION = 10000; let CONFIG_MODE = false;

// ACTION FUNCTION let btnAction = {     buttonPress: function (_local_name, _BTHparsed) {         local_name = _local_name;         BTHparsedd = _BTHparsed;

        if (BTHparsedd.Button === 1) {             // Single Push             Shelly.call("Switch.toggle", { 'id': 0 });         } else if (BTHparsedd.Button === 2) {             // Double Push             Shelly.call("Switch.set", { 'id': 0, 'on': true });         } else if (BTHparsedd.Button === 3) {             // Triple Push             Shelly.call("Switch.set", { 'id': 0, 'on': false });             //program.startBLEScan(); // Start Pairing Mode         } else if (BTHparsedd.Button === 4) {             // Long Push             program.startBLEScan(); // Start Pairing Mode             //Shelly.call("Shelly.Reboot", {}); // Reboot the device             //Shelly.call("Switch.set", { 'id': 0, 'on': true, toggle_after: 0.25 });         }     } };

let KVSbtn = {     loadAllButtons: function () {         Shelly.call("KVS.GetMany", {             match: ALLTERCO_DEVICE_NAME_PREFIX + ":"         }, function (result, error_code, error_message) {             if (error_code !== 0) {                 return null;             }             buttons_cache = [];             for (let item in result.items) {                 if (typeof result.items[item] === "undefined") continue;                 buttons_cache.push(result.items[item].value);             }             console.log("Buttons loaded ", buttons_cache.length);         });     },     getAllButtons: function () {         Shelly.call("KVS.GetMany", {             match: ALLTERCO_DEVICE_NAME_PREFIX + ":"         }, function (result, error_code, error_message) {             if (error_code !== 0) {                 return null;             }             for (let item in result.items) {                 if (typeof result.items[item] === "undefined") continue;                 let buttons = [];                 buttons.push({ name: item, addr: result.items[item].value });                 print(JSON.stringify(buttons));             }             return result.items;         });     },     getButton: function (btnName, callback) {         Shelly.call("KVS.Get", {             key: btnName,         }, function (result, error_code, error_message, callback) {             if (error_code !== 0) {                 console.log('Button not found');             } else {                 callback();             }         }, callback);     },     deleteButton: function (btnName) {         Shelly.call("KVS.Delete", {             key: btnName,         }, function (result, error_code, error_message, btnName) {             if (error_code === 0) {                 print("Button deleted: ", btnName);             } else {                 print("Button not found: ", btnName);             }             return error_code === 0;         }, btnName);     },     registerButton: function (btnName, addr) {         Shelly.call("KVS.Set", {             key: btnName,             value: addr         }, function (result, error_code, error_message, btnName) {             if (error_code === 0) {                 print("Button registered successfully: ", btnName);             } else {                 print("Button not registered: ", btnName);             }             return error_code === 0;         }, btnName);     },     registerButtonCheck: function (btnName, addr) {         let button1 = { name: btnName, addr: addr };         console.log(JSON.stringify(button1));         Shelly.call("KVS.Get", {             key: btnName         }, function (result, error_code, error_message, button) {             if (error_code !== 0) {                 KVSbtn.registerButton(btnName, button.addr);             } else {                 print("Button already registered: ", btnName);             }         }, button1);     } };

// BLUETOOTH SCANNER // BLE DECODING FUNCTIONS ----------------------------- 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;     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) {         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;         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;             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;     }, };

function replace(origin, substr, replace) {     while (origin.indexOf(substr) !== -1) {         origin = origin.slice(0, origin.indexOf(substr)) + replace + origin.slice(origin.indexOf(substr) + substr.length, origin.length);     }     return origin; } // END BLE DECODING FUNCTIONS -----------------------------------

let last_packet_id = 0x100;

// SCAN OF PAIRING / UNPAIRING BUTTONS (One click for Pairing, Double click for Unpairing) function scanBLE(ev, res) {     let found = false;     if (ev !== BLE.Scanner.SCAN_RESULT) return;     if (typeof res.service_data === 'undefined' || typeof res.service_data[BTHOME_SVC_ID_STR] === 'undefined') return;     if (typeof res.local_name !== 'string') return;     if (res.local_name.indexOf(ALLTERCO_DEVICE_NAME_PREFIX) !== 0) return;

    let BTHparsed = ShellyBLUParser.getData(res);     if (BTHparsed === null) {         console.log("Failed to parse BTH data");         return;     }     if (last_packet_id === BTHparsed.pid) return;     last_packet_id = BTHparsed.pid;

    for (let item in buttons_cache) {         if (buttons_cache[item] === res.addr)             found = true;     }     if (found === false && CONFIG_MODE === false) return;

    console.log("Shelly BTH packet: ", JSON.stringify(BTHparsed));     bleReceived(BTHparsed, res); }

function bleReceived(BTHparsed, res) {     let btnName = ALLTERCO_DEVICE_NAME_PREFIX + ':' + replace(BTHparsed.addr, ':', '');     if (CONFIG_MODE) {         if (BTHparsed.Button === 1) {             KVSbtn.registerButtonCheck(btnName, BTHparsed.addr);         } else if (BTHparsed.Button === 2) {             KVSbtn.deleteButton(btnName);         }     } else {         btnAction.buttonPress(btnName, BTHparsed);     } }

let program = {     init: function () {         let BLEConfig = Shelly.getComponentConfig('ble');         if (BLEConfig.enable === false) {             console.log('Error: BLE not enabled');         } else {             Timer.set(1000, false, this.startBLEScan);         }     },     startBLEScan: function () {         let bleScanSuccess = BLE.Scanner.Start({ duration_ms: SCAN_DURATION, active: true }, scanBLE);

        if (bleScanSuccess === false) {             Timer.set(1000, false, this.startBLEScan);         } else {             CONFIG_MODE = true;             Timer.set(CONFIG_DURATION, false, program.stopConfigMode);             console.log('Pairing mode started.... (', CONFIG_DURATION / 1000, 's)');         }     },     stopConfigMode: function () {         CONFIG_MODE = false;         console.log('Pairing mode ended');         KVSbtn.loadAllButtons();     } };

program.init();