mirror of https://github.com/espruino/BangleApps
Added BTHome app for firing off BTHome events to HomeAssistant
parent
b97ee0a056
commit
d7a1026562
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
|
@ -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.
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEw4X/AAIHBy06nnnDiHwBRMDrgLJhtXBZM1qvABZHVqtwFxFVqowIhoLBGBE1q35GBHVrkDytyrAuGHIPVroLFFwODrklqoLFLoOCrALHLoIXILoVw+APBBYhdCsEAyphFFwITBgQDBMIgeBqtUgILCSQQuBrflBYW+SQYuBuENBYItB6owCXYUDBYIUBYYYuBh2wBYNQ9cFGAWlq0JsGUgNgy0J1WsEgMWhtwBYXXhWq1YLBkvD4HUgNwnk61Wq2ALBwEAkkBAYPq14kCktsgEMgZmBBIILDqoMBBQOWBIM61ALCrYLBh1WBYMKHgILBqxlBnILC2eqBYVVIAPlrWj1mg9QLDtkDyta1ns2AXEX4Va1c84YLEWYVa1XAhwLJ2B5BBZA6BBZOAC5UA5xHI1E8NYQAFh2g9hrCBY2vQYYAFgSPBF4QAFX4U6cgQLH9S/BAA2qcYYAG9WuPIILHOoKdBBY8D9WvgA"))
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
};
|
|
@ -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!
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -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"}]
|
||||||
|
}
|
|
@ -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();
|
||||||
|
})
|
Loading…
Reference in New Issue