From d7a102656258a77b85830e8c71d0def06b0d0e03 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 6 Feb 2024 16:15:19 +0000 Subject: [PATCH] Added BTHome app for firing off BTHome events to HomeAssistant --- apps/bthome/ChangeLog | 1 + apps/bthome/README.md | 26 +++++++++++ apps/bthome/app-icon.js | 1 + apps/bthome/app.js | 27 +++++++++++ apps/bthome/boot.js | 68 ++++++++++++++++++++++++++++ apps/bthome/clkinfo.js | 17 +++++++ apps/bthome/icon.png | Bin 0 -> 15616 bytes apps/bthome/metadata.json | 20 +++++++++ apps/bthome/settings.js | 91 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 251 insertions(+) create mode 100644 apps/bthome/ChangeLog create mode 100644 apps/bthome/README.md create mode 100644 apps/bthome/app-icon.js create mode 100644 apps/bthome/app.js create mode 100644 apps/bthome/boot.js create mode 100644 apps/bthome/clkinfo.js create mode 100644 apps/bthome/icon.png create mode 100644 apps/bthome/metadata.json create mode 100644 apps/bthome/settings.js diff --git a/apps/bthome/ChangeLog b/apps/bthome/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/bthome/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/bthome/README.md b/apps/bthome/README.md new file mode 100644 index 000000000..d232e8d64 --- /dev/null +++ b/apps/bthome/README.md @@ -0,0 +1,26 @@ +# BTHome + +This uses BTHome (https://bthome.io/) to allow easy control of [Home Assistant](https://www.home-assistant.io/) via Bluetooth advertisements. + +Other apps like [the Home Assistant app](https://banglejs.com/apps/?id=ha) communicate with Home Assistant +via your phone so work from anywhere, but require being in range of your phone. + +## Usage + +When the app is installed, go to the `BTHome` app and click Settings. + +Here, you can choose if you want to advertise your Battery status, but can also click `Add Button`. + +You can then add a custom button event: + +* `Icon` - the picture for the button +* `Name` - the name associated with the button +* `Action` - the action that Home Assistant will see when this button is pressed +* `Button #` - the button event 'number' - keep this at 0 for now + +Once you've saved, you will then get your button shown in the BTHome app. Tapping it will make Bangle.js advertise via BTHome that the button has been pressed. + +## ClockInfo + +When you've added one or more buttons, they will appear in a ClockInfo under the main `Bangle.js` heading. You can just tap to select the ClockInfo, scroll down until a BTHome one is visible and then tap again. It will immediately send the Advertisement. + diff --git a/apps/bthome/app-icon.js b/apps/bthome/app-icon.js new file mode 100644 index 000000000..ecdc205bc --- /dev/null +++ b/apps/bthome/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4X/AAIHBy06nnnDiHwBRMDrgLJhtXBZM1qvABZHVqtwFxFVqowIhoLBGBE1q35GBHVrkDytyrAuGHIPVroLFFwODrklqoLFLoOCrALHLoIXILoVw+APBBYhdCsEAyphFFwITBgQDBMIgeBqtUgILCSQQuBrflBYW+SQYuBuENBYItB6owCXYUDBYIUBYYYuBh2wBYNQ9cFGAWlq0JsGUgNgy0J1WsEgMWhtwBYXXhWq1YLBkvD4HUgNwnk61Wq2ALBwEAkkBAYPq14kCktsgEMgZmBBIILDqoMBBQOWBIM61ALCrYLBh1WBYMKHgILBqxlBnILC2eqBYVVIAPlrWj1mg9QLDtkDyta1ns2AXEX4Va1c84YLEWYVa1XAhwLJ2B5BBZA6BBZOAC5UA5xHI1E8NYQAFh2g9hrCBY2vQYYAFgSPBF4QAFX4U6cgQLH9S/BAA2qcYYAG9WuPIILHOoKdBBY8D9WvgA")) \ No newline at end of file diff --git a/apps/bthome/app.js b/apps/bthome/app.js new file mode 100644 index 000000000..7e9a39531 --- /dev/null +++ b/apps/bthome/app.js @@ -0,0 +1,27 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +function showMenu() { + var settings = require("Storage").readJSON("bthome.json",1)||{}; + if (!(settings.buttons instanceof Array)) + settings.buttons = []; + var menu = { "": {title:"BTHome", back:load} }; + settings.buttons.forEach((button,idx) => { + var img = require("icons").getIcon(button.icon); + menu[/*LANG*/"\0"+img+" "+button.name] = function() { + Bangle.btHome([{type:"button_event",v:button.v,n:button.n}],{event:true}); + E.showMenu(); + E.showMessage("Sending Event"); + Bangle.buzz(); + setTimeout(showMenu, 500); + }; + }); + menu[/*LANG*/"Settings"] = function() { + eval(require("Storage").read("bthome.settings.js"))(()=>showMenu()); + }; + E.showMenu(menu); +} + +showMenu(); + + diff --git a/apps/bthome/boot.js b/apps/bthome/boot.js new file mode 100644 index 000000000..9c02581fe --- /dev/null +++ b/apps/bthome/boot.js @@ -0,0 +1,68 @@ +// Ensure we have the bleAdvert global (to play well with other stuff) +if (!Bangle.bleAdvert) Bangle.bleAdvert = {}; +Bangle.btHomeData = []; +{ + require("BTHome").packetId = 0|(Math.random()*256); // random packet id so new packets show up + let settings = require("Storage").readJSON("bthome.json",1)||{}; + if (settings.showBattery) + Bangle.btHomeData.push({ + type : "battery", + v : E.getBattery() + }); + // If buttons defined, add events for them + if (settings.buttons instanceof Array) { + let n = settings.buttons.reduce((n,b)=>b.n>n?b.n:n,-1); + for (var i=0;i<=n;i++) + Bangle.btHomeData.push({type:"button_event",v:"none",n:n}); + } +} + +/* Global function to allow advertising BTHome adverts + extras = array of extra data, see require("BTHome").getAdvertisement - can add {n:0/1/2} for different instances + options = { event : an event - advertise fast, and when connected + } +*/ +Bangle.btHome = function(extras, options) { + options = options||{}; + if(extras) { // update with extras + extras.forEach(extra => { + var n = Bangle.btHomeData.find(b=>b.type==extra.type && b.n==extra.n); + if (n) Object.assign(n, extra); + else Bangle.btHomeData.push(extra); + }); + } + var bat = Bangle.btHomeData.find(b=>b.type=="battery"); + if (bat) bat.v = E.getBattery(); + var advert = require("BTHome").getAdvertisement(Bangle.btHomeData)[0xFCD2]; + // Add to the list of available advertising + if(Array.isArray(Bangle.bleAdvert)){ + var found = false; + for(var ad in Bangle.bleAdvert){ + if(ad[0xFCD2]){ + ad[0xFCD2] = advert; + found = true; + break; + } + } + if(!found) + Bangle.bleAdvert.push({ 0xFCD2: advert }); + } else { + Bangle.bleAdvert[0xFCD2] = advert; + } + var advOptions = {}; + var updateTimeout = 10*60*1000; // update every 10 minutes + if (options.event) { // if it's an event... + advOptions.interval = 50; + advOptions.whenConnected = true; + updateTimeout = 30000; // slow down in 30 seconds + } + NRF.setAdvertising(Bangle.bleAdvert, advOptions); + if (Bangle.btHomeTimeout) clearTimeout(Bangle.btHomeTimeout); + Bangle.btHomeTimeout = setTimeout(function() { + delete Bangle.btHomeTimeout; + // clear events + Bangle.btHomeData.forEach(d => {if (d.type=="button_event") d.v="none";}); + // update + Bangle.btHome(); + },updateTimeout); +}; diff --git a/apps/bthome/clkinfo.js b/apps/bthome/clkinfo.js new file mode 100644 index 000000000..8698c9828 --- /dev/null +++ b/apps/bthome/clkinfo.js @@ -0,0 +1,17 @@ +(function() { + var settings = require("Storage").readJSON("bthome.json",1)||{}; + if (!(settings.buttons instanceof Array)) + settings.buttons = []; + return { + name: "Bangle", + items: settings.buttons.map(button => { + return { name : button.name, + get : function() { return { text : button.name, + img : require("icons").getIcon(button.icon) }}, + show : function() {}, + hide : function() {}, + run : function() { Bangle.btHome([{type:"button_event",v:button.v,n:button.n}],{event:true}); } + } + }) + }; +}) // must not have a semi-colon! \ No newline at end of file diff --git a/apps/bthome/icon.png b/apps/bthome/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..091784477c391787237d686ed17b8d251eb0cd5f GIT binary patch literal 15616 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 literal 0 HcmV?d00001 diff --git a/apps/bthome/metadata.json b/apps/bthome/metadata.json new file mode 100644 index 000000000..643e24a63 --- /dev/null +++ b/apps/bthome/metadata.json @@ -0,0 +1,20 @@ +{ "id": "bthome", + "name": "BTHome", + "shortName":"BTHome", + "version":"0.01", + "description": "Allow your Bangle to advertise with BTHome and send events to Home Assistant via Bluetooth", + "icon": "icon.png", + "type": "app", + "tags": "clkinfo,bthome,bluetooth", + "supports" : ["BANGLEJS2"], + "dependencies": {"textinput":"type"}, + "readme": "README.md", + "storage": [ + {"name":"bthome.img","url":"app-icon.js","evaluate":true}, + {"name":"bthome.clkinfo.js","url":"clkinfo.js"}, + {"name":"bthome.boot.js","url":"boot.js"}, + {"name":"bthome.app.js","url":"app.js"}, + {"name":"bthome.settings.js","url":"settings.js"} + ], + "data":[{"name":"bthome.json"}] +} diff --git a/apps/bthome/settings.js b/apps/bthome/settings.js new file mode 100644 index 000000000..70f50f2ac --- /dev/null +++ b/apps/bthome/settings.js @@ -0,0 +1,91 @@ +(function(back) { + var settings = require("Storage").readJSON("bthome.json",1)||{}; + if (!(settings.buttons instanceof Array)) + settings.buttons = []; + + function saveSettings() { + require("Storage").writeJSON("bthome.json",settings) + } + + function showButtonMenu(button, isNew) { + var isNew = false; + if (!button) { + button = {name:"home", icon:"home", n:0, v:"press"}; + isNew = true; + } + var actions = ["press","double_press","triple_press","long_press","long_double_press","long_triple_press"]; + var menu = { + "":{title:isNew ? /*LANG*/"New Button" : /*LANG*/"Edit Button", back:showMenu}, + /*LANG*/"Icon" : { + value : "\0"+require("icons").getIcon(button.icon), + onchange : () => { + require("icons").showIconChooser().then(function(iconName) { + button.icon = iconName; + button.name = iconName; + showButtonMenu(button, isNew); + }, function() { + showButtonMenu(button, isNew); + }); + } + }, + /*LANG*/"Name" : { + value : button.name, + onchange : () => { + require("textinput").input({text:button.name}).then(function(name) { + button.name = name; + showButtonMenu(button, isNew); + }, function() { + showButtonMenu(button, isNew); + }); + } + }, + /*LANG*/"Action" : { + value : Math.max(0,actions.indexOf(button.v)), min:0, max:actions.length-1, + format : v => actions[v], + onchange : v => button.v=actions[v] + }, + /*LANG*/"Button #" : { + value : button.n, min:0, max:3, + onchange : v => button.n=v + }, + /*LANG*/"Save" : () => { + settings.buttons.push(button); + saveSettings(); + showMenu(); + } + }; + if (!isNew) menu[/*LANG*/"Delete"] = function() { + E.showPrompt("Delete Button?").then(function(yes) { + if (yes) { + settings.buttons.splice(settings.buttons.indexOf(button),1); + saveSettings(); + } + showMenu(); + }); + } + E.showMenu(menu); + } + + function showMenu() { + var menu = { "": {title:"BTHome", back:back}, + /*LANG*/"Show Battery" : { + value : !!settings.showBattery, + onchange : v=>{ + settings.showBattery = v; + saveSettings(); + } + } + }; + settings.buttons.forEach((button,idx) => { + var img = require("icons").getIcon(button.icon); + menu[/*LANG*/"Button"+(img ? " \0"+img : (idx+1))] = function() { + showButtonMenu(button, false); + }; + }); + menu[/*LANG*/"Add Button"] = function() { + showButtonMenu(undefined, true); + }; + E.showMenu(menu); + } + showMenu(); +}) \ No newline at end of file