Merge pull request #3246 from ellabellla/master

New app, hassio: Home Assistant API Interface
master
Rob Pilling 2024-03-11 22:03:28 +00:00 committed by GitHub
commit a496a7db60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 423 additions and 0 deletions

1
apps/hassio/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

41
apps/hassio/README.md Normal file
View File

@ -0,0 +1,41 @@
# Home Assistant API Interface
This app provides two features:
- Sending health, compass, accelerator, and battery information to [Home Assistant](https://www.home-assistant.io/)
- Displaying [Home Assistant](https://www.home-assistant.io/) templates
This is done through rest api calls to your [Home Assistant](https://www.home-assistant.io/) server. This means the app requires using the [Android Integration](/?id=android) and for your server to be accessible from your phone.
A restart may be required after loading the app to start the background sensor process.
## Configuration
Configuration is done through modifying the settings json.
```json
{
"templates": [
{
"name":"Test Template",
"temp":"Test"
}
],
"interval": 180000,
"api_key":"api_key",
"host":"https://homeassistant:8123",
"id":"banglejs",
"friendly_name":"Banglejs Sensors"
}
```
- `api_key`: A [Home Assistant](https://www.home-assistant.io/) [api key](https://developers.home-assistant.io/docs/api/rest/).
- `host`: The url of your [Home Assistant](https://www.home-assistant.io/) server. The url must be https or it will not work. This is a limitation of the permissions given to the Banglejs GadgetBridge app. You can compile a custom version if you wish to modify this.
- `interval`: The sensor update interval.
- `id`: An id to be used for identifying your banglejs in [Home Assistant](https://www.home-assistant.io/).
- `friendly_name`: The name [Home Assistant](https://www.home-assistant.io/) will use to refer to your banglejs.
- `templates`: A list of templates to display in the gui. They are given in this format `{"name":"Template Name", "temp":"A template"}`. More information about creating templates can be found [here](https://www.home-assistant.io/docs/configuration/templating/).
## The GUI
The GUI will display templates one at a time. Tap to go to the next template. Long press to reload the current template.

116
apps/hassio/hassio.app.js Normal file
View File

@ -0,0 +1,116 @@
if (HASSIO === undefined) {
loadHassio();
}
function noHassio() {
let Layout = require('Layout');
let layout = new Layout( {
type:"v", c: [
{
type: "txt",
font: "10%",
label: "No settings",
},
],
halign: -1
});
g.clear();
layout.render();
}
const templateGui = () => {
if (HASSIO === undefined) {
noHassio();
return;
}
let selectedTemplate = 0;
let Layout = require('Layout');
let layout = new Layout( {
type:"v", c: [
{
type: "txt",
font: "8%",
id: "name",
label: HASSIO.templates[selectedTemplate].name,
},
{
type: 'txt',
font:"10%",
id: "data",
label: "data",
wrap: true,
width: g.getWidth(),
height: g.getHeight()-80,
halign: -1
},
{
type: "txt",
font: "8%",
id: "loc",
label: (selectedTemplate+1) + "/" + HASSIO.templates.length,
}
],
halign: -1
});
const fetchTemplate = (template) => {
const url = `${HASSIO.host}/api/template`;
return Bangle.http(url, {
method: "POST",
timeout: 2000,
body: JSON.stringify({template: template}),
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${HASSIO.api_key}`,
}
});
};
const draw = (selected) => {
setTimeout(() => {
if (selected != selectedTemplate)
return;
fetchTemplate(HASSIO.templates[selectedTemplate].temp).then((data) => {
if (selected != selectedTemplate)
return;
layout.data.label = data.resp;
layout.clear(layout.data);
layout.render();
}, (data) => {
if (selected != selectedTemplate)
return;
layout.data.label = "failed " + JSON.stringify(data);
layout.clear(layout.data);
layout.render();
});
}, 1000);
};
Bangle.on('touch', function(button, xy) {
if (xy.type === 0) {
selectedTemplate++;
if (selectedTemplate >= HASSIO.templates.length)
selectedTemplate = 0;
layout.loc.label = (selectedTemplate+1) + "/" + HASSIO.templates.length;
layout.name.label = HASSIO.templates[selectedTemplate].name
}
layout.data.label = "loading";
g.clear();
layout.render();
draw(selectedTemplate)
});
g.clear();
layout.data.label = "loading";
layout.render();
draw(selectedTemplate);
};
templateGui();

128
apps/hassio/hassio.boot.js Normal file
View File

@ -0,0 +1,128 @@
var HASSIO;
let hassioRunning = false;
function validateHassio(settings) {
const STR_FIELDS = ["api_key", "host", "id", "friendly_name"];
const INT_FIELDS = ["interval"];
const TEMPLATES = "templates";
if (typeof settings !== "object") {
return false;
}
for (const field of STR_FIELDS) {
if (settings[field] === undefined || typeof settings[field] !== "string") {
return false;
}
}
for (const field of INT_FIELDS) {
if (settings[field] === undefined || typeof settings[field] !== "number") {
return false;
}
}
if (settings[TEMPLATES] === undefined || !(settings[TEMPLATES] instanceof Array)) {
return false;
}
for (const template of settings[TEMPLATES]) {
if (template.name === undefined || typeof template.name !== "string") {
return false;
}
if (template.temp === undefined || typeof template.temp !== "string") {
return false;
}
}
return true;
}
const loadHassio = () => {
let hassioSettings = require("Storage").read("hassio.json");
let tmp = HASSIO;
HASSIO = undefined;
if (hassioSettings !== undefined) {
try {
HASSIO = JSON.parse(hassioSettings);
} catch(e) {
}
if (HASSIO !== undefined && !validateHassio(HASSIO)) {
HASSIO = undefined;
}
}
if (HASSIO === undefined) {
HASSIO = tmp;
}
};
loadHassio();
const runHassio = () => {
if (HASSIO !== undefined) {
let hassioAttributes = {
state_class: "measurement",
friendly_name: HASSIO.friendly_name,
unit_of_measurement: "%"
};
const postSensor = (data) => {
const url = `${HASSIO.host}/api/states/sensor.${HASSIO.id}`;
Bangle.http(url, {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${HASSIO.api_key}`,
}
});
};
const getBattery = () => {
const b = E.getBattery(),
c = Bangle.isCharging();
return {
state: c ? "charging" : "discharging",
level: b
};
};
Bangle.on("GPS", (fix) => {
hassioAttributes.gps = fix;
});
const updateSensor = () => {
hassioAttributes.health = Bangle.getHealthStatus();
hassioAttributes.accel = Bangle.getAccel();
hassioAttributes.battery = getBattery();
hassioAttributes.compass = Bangle.getCompass();
postSensor({
state: hassioAttributes.battery.level,
attributes: hassioAttributes
});
};
const log = () => {
Bangle.setCompassPower(true, "hassio");
Bangle.setHRMPower(true, "hassio");
setTimeout(() => {
updateSensor();
Bangle.setCompassPower(false, "hassio");
Bangle.setHRMPower(false, "hassio");
}, 30 * 1000);
};
log();
}
};
(function () {
if (!hassioRunning) {
hassioRunning = true;
setTimeout(() => {
runHassio();
setInterval(runHassio, HASSIO.interval);
}, 5000);
}
})();

BIN
apps/hassio/hassio.img Normal file

Binary file not shown.

13
apps/hassio/hassio.json Normal file
View File

@ -0,0 +1,13 @@
{
"templates": [
{
"name":"Test Template",
"temp":"{% for state in states.weather -%}{%- if loop.first %}The {% elif loop.last %} and the {% else %}, the {% endif -%}{{ state.name | lower }} is {{state.state_with_unit}}{%- endfor %}."
}
],
"interval": 180000,
"api_key":"api_key",
"host":"https://homeassistant:8123",
"id":"banglejs",
"friendly_name":"Banglejs Sensors"
}

BIN
apps/hassio/hassio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

106
apps/hassio/interface.html Normal file
View File

@ -0,0 +1,106 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<h4>Config</h4>
<textarea style="width: 100%;-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box;height:80%;resize: none;" id="json">
</textarea>
<button id="reset" class="btn btn-error">Reload</button>
<button id="save" class="btn btn-primary">Save</button>
<script src="../../core/lib/interface.js"></script>
<script>
const STR_FIELDS = ["api_key", "host", "id", "friendly_name"]
const INT_FIELDS = ["interval"]
const TEMPLATES = "templates"
document.getElementById('reset').addEventListener('click', reset)
document.getElementById('save').addEventListener('click', save);
function validate(str) {
let settings = {};
try {
settings = JSON.parse(str)
} catch (e) {
alert("Unable to parse settings: " + String(e))
return false;
}
if (typeof settings !== "object") {
alert("Settings must be an object")
return false;
}
for (const field of STR_FIELDS) {
if (settings[field] === undefined || typeof settings[field] !== "string") {
alert(`Field ${field} must be a string`)
return false;
}
}
for (const field of INT_FIELDS) {
if (settings[field] === undefined || typeof settings[field] !== "number") {
alert(`Field ${field} must be a number`)
return false;
}
}
if (settings[TEMPLATES] === undefined || !(settings[TEMPLATES] instanceof Array)) {
alert(`Field ${TEMPLATES} must be a list`)
return false;
}
for (const template of settings[TEMPLATES]) {
if (template["name"] === undefined || typeof template["name"] !== "string") {
alert(`Field name of a template must be a string`)
return false;
}
if (template["temp"] === undefined || typeof template["temp"] !== "string") {
alert(`Field name of a template must be a string`)
return false;
}
}
return true;
}
function getData() {
// show loading window
Util.showModal("Loading...");
Util.readStorageJSON('hassio.json', data=>{
// remove window
Util.hideModal();
document.getElementById('json').value = JSON.stringify(data, null, 1);
});
}
function save(){
Util.showModal("Saving...");
let data = document.getElementById('json').value;
if (!validate(data)) {
Util.hideModal();
return;
}
Util.writeStorage("hassio.json", data, () => {
Util.hideModal();
});
}
function reset(){
getData();
}
// Called when app starts
function onInit() {
getData();
}
</script>
</body>
</html>

18
apps/hassio/metadata.json Normal file
View File

@ -0,0 +1,18 @@
{ "id": "hassio",
"name": "Home Assistant API Interface",
"shortName":"Hassio",
"icon": "hassio.png",
"version":"0.01",
"description": "This app gives access to viewing Home Assistant data and sends health, compass, accelerometer, and battery information to Home Assistant as a sensor through the Home Assistant REST API.",
"tags": "tool,sensors",
"supports": ["BANGLEJS2"],
"dependencies": {"android":"app"},
"interface": "interface.html",
"readme": "README.md",
"storage": [
{"name":"hassio.app.js","url":"hassio.app.js"},
{"name":"hassio.boot.js","url":"hassio.boot.js"},
{"name":"hassio.img","url":"hassio.img"}
],
"data": [{"name":"hassio.json"}]
}