forked from FOSS/BangleApps
hadash: initial release
parent
932d2c0d9d
commit
1bc60185dd
|
@ -0,0 +1,4 @@
|
||||||
|
hadash.json
|
||||||
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
package.json
|
|
@ -0,0 +1 @@
|
||||||
|
1.00: initial release
|
|
@ -0,0 +1,67 @@
|
||||||
|
# Home-Assistant Dashboard
|
||||||
|
|
||||||
|
This app interacts with a Home-Assistant (HA) instance. You can query entity
|
||||||
|
states and call services. This allows you access to up-to-date information of
|
||||||
|
any home automation system integrated into HA, and you can also control your
|
||||||
|
automations from your wrist.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
This app uses the REST API to directly interact with HA (which requires a
|
||||||
|
"long-lived access token" - refer to "Configuration").
|
||||||
|
|
||||||
|
You can define a menu structure to be displayed on your Bangle, with the states
|
||||||
|
to be queried and services to be called. Menu entries can be:
|
||||||
|
|
||||||
|
* entry to show the state of a HA entity
|
||||||
|
* entry to call a HA service
|
||||||
|
* sub-menus, including nested sub-menus
|
||||||
|
|
||||||
|
Calls to a service can also have optional input for data fields on the Bangle
|
||||||
|
itself.
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
After installing the app, use the "interface" page (floppy disk icon) in the
|
||||||
|
App Loader to configure it.
|
||||||
|
|
||||||
|
Make sure to set the "Home-Assistant API Base URL" (which must include the
|
||||||
|
"/api" path, as well - but no slash at the end).
|
||||||
|
|
||||||
|
Also create a "long-lived access token" in HA (under the Profile section, at
|
||||||
|
the bottom) and enter it as the "Long-lived access token".
|
||||||
|
|
||||||
|
The tricky bit will be to configure your menu structure. You need to have a
|
||||||
|
basic understanding of the JSON format. The configuration page uses a JSON
|
||||||
|
Editor which will check the syntax and highlight any errors for you. Follow the
|
||||||
|
instructions on the page regarding how to configure menus, menu entries and the
|
||||||
|
required attributes. It also contains examples.
|
||||||
|
|
||||||
|
Once you're happy with the menu structure (and you've entered the base URL and
|
||||||
|
access token), click the "Configure / Upload to Bangle" button.
|
||||||
|
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
The "long-lived access token" will be stored unencrypted on your Bangle. This
|
||||||
|
would - in theory - mean that if your Bangle gets stolen, the new "owner" would
|
||||||
|
have unrestricted access to your Home-Assistant instance (the thief would have
|
||||||
|
to be fairly tech-savvy, though). However, I suggest you create a separate
|
||||||
|
token exclusively for your Bangle - that way, it's very easy to simply delete
|
||||||
|
that token in case your watch is stolen or lost.
|
||||||
|
|
||||||
|
|
||||||
|
## To-Do
|
||||||
|
|
||||||
|
A better way to configure the menu structure would be useful, something like a
|
||||||
|
custom editor (replacing the jsoneditor).
|
||||||
|
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Flaparoo [github](https://github.com/flaparoo)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwgmjhGACyuIxAYUCwIABFyowUCwYwSFwgwSCwowQFwwwQCw4wOFxAwOCxIwMFwuDnAwPCwv//4YFFx0/C4PzGBpXFCwIABMJiMGC5IwGQ4wXJGAq7HC5QwEW4+PCwP4YZTqJFxAwEdBIXKGAQXWIxIXMwAXXBRIXFwYFBnATKC5E/AoPzC6bdKC/4XTx4WCO5CbGC4YuDU5CbGC4QuCma+JKYwECXhoXIFwTsLC5DrONgxzMO4woDFx6nDFIYuPJQoqBFx4ADRIYuSGAiUEGCQXUYg4wUC6YwDBpUIGBYWJwA"))
|
|
@ -0,0 +1,227 @@
|
||||||
|
/*
|
||||||
|
* Home-Assistant Dashboard - Bangle.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
const APP_NAME = 'hadash';
|
||||||
|
|
||||||
|
// Load settings
|
||||||
|
var settings = Object.assign({
|
||||||
|
menu: [
|
||||||
|
{ type: 'state', title: 'Check for updates', id: 'update.home_assistant_core_update' },
|
||||||
|
{ type: 'service', title: 'Create Notification', domain: 'persistent_notification', service: 'create',
|
||||||
|
data: { 'message': 'test notification', 'title': 'Test'} },
|
||||||
|
{ type: 'menu', title: 'Sub-menu', data:
|
||||||
|
[
|
||||||
|
{ type: 'state', title: 'Check for Supervisor updates', id: 'update.home_assistant_supervisor_update' },
|
||||||
|
{ type: 'service', title: 'Restart HA', domain: 'homeassistant', service: 'restart', data: {} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ type: 'service', title: 'Custom Notification', domain: 'persistent_notification', service: 'create',
|
||||||
|
data: { 'title': 'Not via input'},
|
||||||
|
input: { 'message': { options: [], value: 'Pre-filled text' },
|
||||||
|
'notification_id': { options: [ 123, 456, 136 ], value: 999, label: "ID" } } },
|
||||||
|
],
|
||||||
|
HAbaseUrl: '',
|
||||||
|
HAtoken: '',
|
||||||
|
}, require('Storage').readJSON(APP_NAME+'.json', true) || {});
|
||||||
|
|
||||||
|
|
||||||
|
// query an entity state
|
||||||
|
function queryState(title, id, level) {
|
||||||
|
E.showMessage('Fetching entity state from HA', { title: title });
|
||||||
|
Bangle.http(settings.HAbaseUrl+'/states/'+id, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer '+settings.HAtoken,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
}).then(data => {
|
||||||
|
//console.log(data);
|
||||||
|
let HAresp = JSON.parse(data.resp);
|
||||||
|
let title4prompt = title;
|
||||||
|
let msg = HAresp.state;
|
||||||
|
if ('attributes' in HAresp) {
|
||||||
|
if ('friendly_name' in HAresp.attributes)
|
||||||
|
title4prompt = HAresp.attributes.friendly_name;
|
||||||
|
if ('unit_of_measurement' in HAresp.attributes)
|
||||||
|
msg += HAresp.attributes.unit_of_measurement;
|
||||||
|
}
|
||||||
|
E.showPrompt(msg, { title: title4prompt, buttons: {OK: true} }).then((v) => { E.showMenu(menus[level]); });
|
||||||
|
}).catch( error => {
|
||||||
|
console.log(error);
|
||||||
|
E.showPrompt('Error querying state!', { title: title, buttons: {OK: true} }).then((v) => { E.showMenu(menus[level]); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// call a service
|
||||||
|
function callService(title, domain, service, data, level) {
|
||||||
|
E.showMessage('Calling HA service', { title: title });
|
||||||
|
Bangle.http(settings.HAbaseUrl+'/services/'+domain+'/'+service, {
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer '+settings.HAtoken,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
}).then(data => {
|
||||||
|
//console.log(data);
|
||||||
|
E.showPrompt('Service called successfully', { title: title, buttons: {OK: true} }).then((v) => { E.showMenu(menus[level]); });
|
||||||
|
}).catch( error => {
|
||||||
|
console.log(error);
|
||||||
|
E.showPrompt('Error calling service!', { title: title, buttons: {OK: true} }).then((v) => { E.showMenu(menus[level]); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// callbacks for service input menu entries
|
||||||
|
function serviceInputChoiceChange(v, key, entry, level) {
|
||||||
|
entry.input[key].value = entry.input[key].options[v];
|
||||||
|
getServiceInputData(entry, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
function serviceInputFreeform(key, entry, level) {
|
||||||
|
require("textinput").input({text: entry.input[key].value}).then(result => {
|
||||||
|
entry.input[key].value = result;
|
||||||
|
getServiceInputData(entry, level);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// get input data before calling a service
|
||||||
|
function getServiceInputData(entry, level) {
|
||||||
|
let serviceInputMenu = {
|
||||||
|
'': {
|
||||||
|
'title': entry.title,
|
||||||
|
'back': () => E.showMenu(menus[level])
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let CBs = {};
|
||||||
|
for (let key in entry.input) {
|
||||||
|
// pre-fill data with default values
|
||||||
|
if ('value' in entry.input[key])
|
||||||
|
entry.data[key] = entry.input[key].value;
|
||||||
|
|
||||||
|
let label = ( ('label' in entry.input[key] && entry.input[key].label) ? entry.input[key].label : key );
|
||||||
|
let key4CB = key;
|
||||||
|
|
||||||
|
if ('options' in entry.input[key] && entry.input[key].options.length) {
|
||||||
|
// give choice from a selection of options
|
||||||
|
let idx = -1;
|
||||||
|
for (let i in entry.input[key].options) {
|
||||||
|
if (entry.input[key].value == entry.input[key].options[i]) {
|
||||||
|
idx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (idx == -1) {
|
||||||
|
idx = entry.input[key].options.push(entry.input[key].value) - 1;
|
||||||
|
}
|
||||||
|
// the setTimeout method can not be used for the "format" CB since it expects a return value - using eval instead:
|
||||||
|
eval('CBs["'+key+'_format"] = function(v) { return entry.input["'+key+'"].options[v]; }');
|
||||||
|
serviceInputMenu[label] = {
|
||||||
|
value: parseInt(idx),
|
||||||
|
min: 0,
|
||||||
|
max: entry.input[key].options.length - 1,
|
||||||
|
format: CBs[key+'_format'],
|
||||||
|
onchange: (v) => setTimeout(serviceInputChoiceChange, 10, v, key4CB, entry, level)
|
||||||
|
};
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// free-form text input
|
||||||
|
serviceInputMenu[label] = () => setTimeout(serviceInputFreeform, 10, key4CB, entry, level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// menu entry to actually call the service:
|
||||||
|
serviceInputMenu['Call service'] = function() { callService(entry.title, entry.domain, entry.service, entry.data, level); };
|
||||||
|
E.showMenu(serviceInputMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// menu hierarchy
|
||||||
|
var menus = [];
|
||||||
|
|
||||||
|
|
||||||
|
// add menu entries
|
||||||
|
function addMenuEntries(level, entries) {
|
||||||
|
for (let i in entries) {
|
||||||
|
let entry = entries[i];
|
||||||
|
let entryCB;
|
||||||
|
|
||||||
|
// is there a menu entry title?
|
||||||
|
if (! ('title' in entry) || ! entry.title)
|
||||||
|
entry.title = 'TBD';
|
||||||
|
|
||||||
|
switch (entry.type) {
|
||||||
|
case 'state':
|
||||||
|
/*
|
||||||
|
* query entity state
|
||||||
|
*/
|
||||||
|
if ('id' in entry && entry.id) {
|
||||||
|
entryCB = () => setTimeout(queryState, 10, entry.title, entry.id, level);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'service':
|
||||||
|
/*
|
||||||
|
* call HA service
|
||||||
|
*/
|
||||||
|
if ('domain' in entry && entry.domain && 'service' in entry && entry.service) {
|
||||||
|
if (! ('data' in entry))
|
||||||
|
entry.data = {};
|
||||||
|
if ('input' in entry) {
|
||||||
|
// get input for some data fields first
|
||||||
|
entryCB = () => setTimeout(getServiceInputData, 10, entry, level);
|
||||||
|
} else {
|
||||||
|
// call service straight away
|
||||||
|
entryCB = () => setTimeout(callService, 10, entry.title, entry.domain, entry.service, entry.data, level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'menu':
|
||||||
|
/*
|
||||||
|
* sub-menu
|
||||||
|
*/
|
||||||
|
entryCB = () => setTimeout(showSubMenu, 10, level + 1, entry.title, entry.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only attach a call-back to menu entry if it's properly configured
|
||||||
|
if (! entryCB) {
|
||||||
|
menus[level][entry.title + ' - not correctly configured!'] = {};
|
||||||
|
} else {
|
||||||
|
menus[level][entry.title] = entryCB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// create and show a sub menu
|
||||||
|
function showSubMenu(level, title, entries) {
|
||||||
|
menus[level] = {
|
||||||
|
'': {
|
||||||
|
'title': title,
|
||||||
|
'back': () => E.showMenu(menus[level - 1])
|
||||||
|
},
|
||||||
|
};
|
||||||
|
addMenuEntries(level, entries);
|
||||||
|
E.showMenu(menus[level]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* create the main menu
|
||||||
|
*/
|
||||||
|
menus[0] = {
|
||||||
|
'': {
|
||||||
|
'title': 'HA-Dash',
|
||||||
|
'back': () => load()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
addMenuEntries(0, settings.menu);
|
||||||
|
|
||||||
|
// check required configuration
|
||||||
|
if (! settings.HAbaseUrl || ! settings.HAtoken) {
|
||||||
|
E.showAlert('The app is not yet configured!', 'HA-Dash').then(() => E.showMenu(menus[0]));
|
||||||
|
} else {
|
||||||
|
E.showMenu(menus[0]);
|
||||||
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,258 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="HAbaseUrl">Home-Assistant API Base URL:</label>
|
||||||
|
<input class="form-input" type="text" id="HAbaseUrl" placeholder="https://ha.example:8123/api" />
|
||||||
|
<div>
|
||||||
|
<small class="text-muted">Make sure to include "/api" as the URL path, but no slash at the end.</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="HAtoken">Long-lived access token:</label>
|
||||||
|
<input class="form-input" type="text" id="HAtoken" placeholder="Your Long-lived Access Token" />
|
||||||
|
<div>
|
||||||
|
<small class="text-muted">It's recommended to create a dedicated token for your Bangle.</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
<h4>Menu structure</h4>
|
||||||
|
|
||||||
|
<p>Use the editor below to configure the menu structure displayed in the
|
||||||
|
Bangle app. It is in the JSON format.</p>
|
||||||
|
|
||||||
|
<p>The main menu, and any sub-menus, are arrays. They can contain 3
|
||||||
|
different types of entries (objects) defined by the "type" attribute:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Query an Entity State</b>
|
||||||
|
<pre class="code" data-lang="JSON">
|
||||||
|
{
|
||||||
|
"type": "state",
|
||||||
|
"title": "Menu entry title",
|
||||||
|
"id": "HA Entity ID"
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
The required Entity ID can be looked up in HA under Settings ->
|
||||||
|
Devices & Services -> Entities. For example:
|
||||||
|
<pre class="code" data-lang="JSON">
|
||||||
|
{
|
||||||
|
"type": "state",
|
||||||
|
"title": "Check for updates",
|
||||||
|
"id": "update.home_assistant_core_update"
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Call a HA service</b>
|
||||||
|
<pre class="code" data-lang="JSON">
|
||||||
|
{
|
||||||
|
"type": "service",
|
||||||
|
"title": "Menu entry title",
|
||||||
|
"domain": "HA Domain",
|
||||||
|
"service": "HA Service",
|
||||||
|
"data": {
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
<p>The required information to call a HA service can be found in HA
|
||||||
|
under the Developer tools -> Services. Use the "Go to YAML Mode"
|
||||||
|
function to see the actual names and values. The domain and service
|
||||||
|
parts are the 2 parts of the service name which are separated by a dot.
|
||||||
|
Any (optional) data key/value pairs can be added under the "data"
|
||||||
|
field. For example, here's a service call YAML:
|
||||||
|
<pre class="code" data-lang="YAML">
|
||||||
|
service: persistent_notification.create
|
||||||
|
data:
|
||||||
|
message: test Notification
|
||||||
|
title: Test
|
||||||
|
</pre>
|
||||||
|
The resulting menu entry (JSON object) should be:
|
||||||
|
<pre class="code" data-lang="JSON">
|
||||||
|
{
|
||||||
|
"type": "service",
|
||||||
|
"title": "Create Notification",
|
||||||
|
"domain": "persistent_notification",
|
||||||
|
"service": "create",
|
||||||
|
"data": {
|
||||||
|
"message": "test Notification",
|
||||||
|
"title": "Test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
If the service requires a target, include the
|
||||||
|
"entity_id"/"device_id"/etc. (listed under "target:") also as "data"
|
||||||
|
key/value pairs. For example, if the YAML also includes:
|
||||||
|
<pre class="code" data-lang="YAML">
|
||||||
|
target:
|
||||||
|
device_id: abcd1234
|
||||||
|
</pre>
|
||||||
|
... add another "data" key/value pair: <code>"device_id": "abcd1234"</code>.
|
||||||
|
If that doesn't work, list the device (or entity) ID in an array:
|
||||||
|
<code>"device_id": [ "abcd1234" ]</code></p>
|
||||||
|
|
||||||
|
<p>Data fields can also have variable input on the Bangle. In that
|
||||||
|
case, don't add the key/value pair under "data", but create an "input"
|
||||||
|
object with entries per "data" key:
|
||||||
|
<pre class="code" data-lang="JSON">
|
||||||
|
{
|
||||||
|
"key": {
|
||||||
|
"options": [],
|
||||||
|
"value": "",
|
||||||
|
"label": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
If "options" is left empty, the preferred text-input (pick your
|
||||||
|
favourite app - I like the dragboard) is called to allow entering a
|
||||||
|
free-form value. Otherwise, list the allowed values in the "options"
|
||||||
|
array. The "value" is the pre-defined/default value. The "label" is
|
||||||
|
optional and can be used to override the label for this key (as
|
||||||
|
displayed on the Bangle). For example:
|
||||||
|
<pre class="code" data-lang="JSON">
|
||||||
|
{
|
||||||
|
"type": "service",
|
||||||
|
"title": "Custom Notification",
|
||||||
|
"domain": "persistent_notification",
|
||||||
|
"service": "create",
|
||||||
|
"data": {
|
||||||
|
"title": "Fixed"
|
||||||
|
},
|
||||||
|
"input": {
|
||||||
|
"message": {
|
||||||
|
"options": [],
|
||||||
|
"value": "Pre-filled text"
|
||||||
|
},
|
||||||
|
"notification_id": {
|
||||||
|
"options": [
|
||||||
|
"123",
|
||||||
|
"456",
|
||||||
|
"136"
|
||||||
|
],
|
||||||
|
"value": "999",
|
||||||
|
"label": "ID"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
In the above example, the "data" will have 3 key/value pairs when the
|
||||||
|
service is called: the "title" is always the same, "message" can be
|
||||||
|
entered via the (free-form) text-input and "notification_id" can be
|
||||||
|
selected from a list of numbers (however, the prompt will be for "ID"
|
||||||
|
and not "notification_id"). If the default value is not listed in
|
||||||
|
"options" (like "999"), it will be added to that list.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Sub-menu</b>
|
||||||
|
<pre class="code" data-lang="JSON">
|
||||||
|
{
|
||||||
|
"type": "menu",
|
||||||
|
"title": "Menu entry / sub-menu title",
|
||||||
|
"data": []
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
The "data" needs to be another array of menu entries. For example:
|
||||||
|
<pre class="code" data-lang="JSON">
|
||||||
|
{
|
||||||
|
"type": "menu",
|
||||||
|
"title": "Sub-menu",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"type": "state",
|
||||||
|
"title": "Check for Supervisor updates",
|
||||||
|
"id": "update.home_assistant_supervisor_update"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "service",
|
||||||
|
"title": "Restart HA",
|
||||||
|
"domain": "homeassistant",
|
||||||
|
"service": "restart",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
Sub-menus can contain other sub-menus, so you can have multiple levels
|
||||||
|
of nested menus.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div id="jsoneditor"></div>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
<p><div id="status"></div></p>
|
||||||
|
|
||||||
|
<button id="upload" class="btn btn-primary">Configure / Upload to Bangle</button>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var JSONEditorInstance;
|
||||||
|
var JSONEditor_target = document.getElementById('jsoneditor');
|
||||||
|
</script>
|
||||||
|
<script src="jsoneditor.bundlejs"></script>
|
||||||
|
|
||||||
|
<script src="../../core/lib/interface.js"></script>
|
||||||
|
<script>
|
||||||
|
var settings = {
|
||||||
|
menu: [
|
||||||
|
{ type: 'state', title: 'Check for updates', id: 'update.home_assistant_core_update' },
|
||||||
|
{ type: 'service', title: 'Create Notification', domain: 'persistent_notification', service: 'create',
|
||||||
|
data: { 'message': 'test notification', 'title': 'Test'} },
|
||||||
|
{ type: 'menu', title: 'Sub-menu', data:
|
||||||
|
[
|
||||||
|
{ type: 'state', title: 'Check for Supervisor updates', id: 'update.home_assistant_supervisor_update' },
|
||||||
|
{ type: 'service', title: 'Restart HA', domain: 'homeassistant', service: 'restart', data: {} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ type: 'service', title: 'Custom Notification', domain: 'persistent_notification', service: 'create',
|
||||||
|
data: { 'title': 'Not via input'},
|
||||||
|
input: { 'message': { options: [], value: 'Pre-filled text' },
|
||||||
|
'notification_id': { options: [ 123, 456, 136 ], value: 999, label: "ID" } } },
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
function onInit() {
|
||||||
|
// read in existing settings from the Bangle
|
||||||
|
try {
|
||||||
|
Util.readStorageJSON('hadash.json', currentSettings => {
|
||||||
|
if (currentSettings) {
|
||||||
|
settings = currentSettings;
|
||||||
|
if ('HAbaseUrl' in settings)
|
||||||
|
document.getElementById('HAbaseUrl').value = settings.HAbaseUrl;
|
||||||
|
if ('HAtoken' in settings)
|
||||||
|
document.getElementById('HAtoken').value = settings.HAtoken;
|
||||||
|
if ('menu' in settings)
|
||||||
|
JSONEditorInstance.update({ text: undefined, json: settings.menu });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed to read existing settings: "+e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("upload").addEventListener("click", function() {
|
||||||
|
if (JSONEditorInstance.get().json) {
|
||||||
|
settings.menu = JSONEditorInstance.get().json;
|
||||||
|
} else {
|
||||||
|
settings.menu = JSON.parse(JSONEditorInstance.get().text);
|
||||||
|
}
|
||||||
|
if (! settings.menu) {
|
||||||
|
document.getElementById("status").innerHTML = 'Generating the menu failed (or menu is empty) - upload aborted!';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settings.HAbaseUrl = document.getElementById('HAbaseUrl').value;
|
||||||
|
settings.HAtoken = document.getElementById('HAtoken').value;
|
||||||
|
Util.writeStorage('hadash.json', JSON.stringify(settings), () => {
|
||||||
|
document.getElementById("status").innerHTML = 'HA-Dash configuration successfully uploaded to Bangle!';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* JSONEditor wrapper
|
||||||
|
*
|
||||||
|
* This script is bundled together with the actual JSONEditor (https://github.com/josdejong/svelte-jsoneditor)
|
||||||
|
* using ESBuild (see below).
|
||||||
|
*
|
||||||
|
* The following global variables need to be defined before including the jsoneditor-bundle.js:
|
||||||
|
*
|
||||||
|
* JSONEditorInstance will contain the new JSONEditor instance
|
||||||
|
* JSONEditor_target element ID of container (<div>) for the JSONEditor
|
||||||
|
*
|
||||||
|
* To build the bundle, run the following commands:
|
||||||
|
* npm install esbuild
|
||||||
|
* npm install vanilla-jsoneditor
|
||||||
|
* ./node_modules/.bin/esbuild jsoneditor.wrapperjs --bundle --outfile=jsoneditor.bundlejs
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { JSONEditor } from 'vanilla-jsoneditor/standalone.js'
|
||||||
|
|
||||||
|
JSONEditorInstance = new JSONEditor({ target: JSONEditor_target, props: {} });
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"id": "hadash",
|
||||||
|
"name": "Home-Assistant Dashboard",
|
||||||
|
"shortName":"HA-Dash",
|
||||||
|
"version":"1.00",
|
||||||
|
"description": "Interact with Home-Assistant (query states, call services)",
|
||||||
|
"icon": "hadash.png",
|
||||||
|
"screenshots": [{ "url": "screenshot.png" }],
|
||||||
|
"type": "app",
|
||||||
|
"tags": "tool,online",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"dependencies": { "textinput": "type" },
|
||||||
|
"readme": "README.md",
|
||||||
|
"interface": "interface.html",
|
||||||
|
"storage": [
|
||||||
|
{ "name":"hadash.app.js", "url":"hadash.app.js" },
|
||||||
|
{ "name":"hadash.img", "url":"hadash-icon.js", "evaluate":true }
|
||||||
|
],
|
||||||
|
"data": [{ "name":"hadash.json" }]
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
Loading…
Reference in New Issue