New app: hasensors: Home Assistant Sensors

Creates battery level+state sensors in Home Assistant over HTTP
pull/2574/head
Richard de Boer 2023-02-12 19:17:16 +01:00
parent 716af7c18b
commit f38518fb63
No known key found for this signature in database
7 changed files with 176 additions and 0 deletions

1
apps/hasensors/ChangeLog Normal file
View File

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

24
apps/hasensors/README.md Normal file
View File

@ -0,0 +1,24 @@
# Home Assistant Sensors
Sends sensor values to [Home Assistant](https://www.home-assistant.io/) using
the [Android Integration](/?id=android).
It doesn't use the Home Assistant app on your phone, but posts directly to
Home Assistant, so it only works if Home Assistant can be reached from your phone.
## Setup
You need to fill out these fields:
* *Sensor ID*: This is prefixed to sensor IDs in Home Assistant.
If you set this to `banglejs`, the battery sensor will be named `sensor.banglejs_battery_level`.
* *Sensor Name*: This is prefixed to human-friendly sensor names.
If you set this to `Bangle.js`, the battery sensor will show as `Bangle.js Battery Level`.
* *Home Assistant URL*: The URL of your Home Assistant Installation.
* *Authentication Token*: You need to generate a Long-Lived Access Token in
Home Assistant, at the bottom of [your profile page](https://my.home-assistant.io/redirect/profile/).
## Features
Currently creates these sensors:
* `<sensor id>_battery_level`: Your watch battery level as percentage
* `<sensor id>_battery_state`: `charging` or `discharging`

6
apps/hasensors/boot.js Normal file
View File

@ -0,0 +1,6 @@
(function () {
const sb = () => require('hasensors').sendBattery();
Bangle.on("charging", sb);
NRF.on("connect", () => setTimeout(sb, 2000));
setInterval(sb, 10 * 60 * 1000);
})();

View File

@ -0,0 +1,89 @@
<html lang="en">
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
<title>Home Assistant Sensors configuration</title>
<style>
label {
display: block;
margin-bottom: 1em;
}
label span:first-child {
display: inline-block;
min-width: 20ex;
}
label span.explanation {
display: block;
margin-left: 21ex;
}
input:invalid {
outline: 1px solid red;
background: lightcoral;
}
</style>
</head>
<body>
<form id="sensorform">
<label><span>Sensor ID:</span>
<input name="id" size="10" value="banglejs" required pattern="[_a-z0-9]+">
<span class="explanation">Lowercase letters, numbers or underscores.</span></label>
<label><span>Sensor Name:</span>
<input name="name" size="10" value="Bangle.js" required>
<span class="explanation">Human-friendly sensor name.</span></label>
<label><span>Home Assistant URL:</span>
<input name="url" type="url" placeholder="http://home_assistant_url:8123" required>
<span class="explanation">Needs to be reachable by your phone.</span></label>
<label><span>Authentication Token:</span>
<input name="token" required pattern="[\-_\.0-9a-zA-Z]+">
<span class="explanation">Create a long-lived access token in Home Assistant at the bottom of
<a href="https://my.home-assistant.io/redirect/profile/" target="_blank">your user profile</a>.</span></label>
</form>
<p>
<button id="upload" class="btn btn-primary">Upload</button>
</p>
<script src="../../core/lib/customize.js"></script>
<script>
const STORAGE_KEY = 'hasensors-config';
const fields = ['id', 'name', 'url', 'token'];
const form = document.getElementById('sensorform');
let stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
try {
stored = JSON.parse(stored);
} catch (_) {
stored = undefined;
}
}
if (stored) {
for (const field of fields) {
form[field].value = stored[field];
}
}
document.getElementById("upload").addEventListener("click", function () {
let config = {};
for (const field of fields) {
if (!form[field].validity.valid) {
form[field].focus();
alert("Please enter a valid " + field);
return;
}
}
for (const field of fields) {
config[field] = form[field].value
}
console.log('config:', config, JSON.stringify(config));
localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
sendCustomizedApp({
id: "hasensors",
storage: [
{name: "hasensors.boot.js", url: "boot.js"},
{name: "hasensors", url: "lib.js"},
{name: "hasensors.settings.json", content: JSON.stringify(config)},
]
});
});
</script>
</body>
</html>

BIN
apps/hasensors/ha.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

35
apps/hasensors/lib.js Normal file
View File

@ -0,0 +1,35 @@
// split out into a separate file to keep bootcode short.
function s(key) {
return (require('Storage').readJSON('hasensors.settings.js', true) || {})[key];
}
function post(sensor, data) {
const url = s('url') + '/api/states/sensor.' + s('id') + '_' + sensor;
Bangle.http(url, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + s('token'),
}
});
}
exports.sendBattery = function () {
if (!NRF.getSecurityStatus().connected) return;
post('battery_level', {
state: E.getBattery(),
attributes: {
friendly_name: s('name') + " Battery Level",
unit_of_measurement: "%",
device_class: "battery",
state_class: "measurement",
}
});
post('battery_state', {
state: Bangle.isCharging() ? 'charging' : 'discharging',
attributes: {
friendly_name: s('name') + " Battery State",
}
});
}

View File

@ -0,0 +1,21 @@
{
"id": "hasensors",
"name": "Home Assistant Sensors",
"shortName": "HA sensors",
"version": "0.01",
"description": "Send sensor values to Home Assistant using the Android Integration.",
"icon": "ha.png",
"type": "bootloader",
"tags": "tool,sensors",
"dependencies": {"android":"app"},
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"custom": "custom.html",
"storage": [
{"name":"hasensors","url":"lib.js"},
{"name":"hasensors.boot.js","url":"boot.js"}
],
"data": [
{"name":"hasensors.settings.json"}
]
}