Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BLU direct controll through bluetooth #111

Open
freggl8 opened this issue Aug 1, 2024 · 1 comment
Open

BLU direct controll through bluetooth #111

freggl8 opened this issue Aug 1, 2024 · 1 comment

Comments

@freggl8
Copy link

freggl8 commented Aug 1, 2024

No description provided.

@freggl8
Copy link
Author

freggl8 commented Aug 1, 2024

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();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant