diff --git a/apps/hassio/ChangeLog b/apps/hassio/ChangeLog new file mode 100644 index 000000000..2286a7f70 --- /dev/null +++ b/apps/hassio/ChangeLog @@ -0,0 +1 @@ +0.01: New App! \ No newline at end of file diff --git a/apps/hassio/README.md b/apps/hassio/README.md new file mode 100644 index 000000000..7416bd90a --- /dev/null +++ b/apps/hassio/README.md @@ -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. diff --git a/apps/hassio/hassio.app.js b/apps/hassio/hassio.app.js new file mode 100644 index 000000000..f12393773 --- /dev/null +++ b/apps/hassio/hassio.app.js @@ -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(); \ No newline at end of file diff --git a/apps/hassio/hassio.boot.js b/apps/hassio/hassio.boot.js new file mode 100644 index 000000000..14aa34f6a --- /dev/null +++ b/apps/hassio/hassio.boot.js @@ -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); + } +})(); \ No newline at end of file diff --git a/apps/hassio/hassio.img b/apps/hassio/hassio.img new file mode 100644 index 000000000..49fea5b50 Binary files /dev/null and b/apps/hassio/hassio.img differ diff --git a/apps/hassio/hassio.json b/apps/hassio/hassio.json new file mode 100644 index 000000000..5f79da858 --- /dev/null +++ b/apps/hassio/hassio.json @@ -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" +} \ No newline at end of file diff --git a/apps/hassio/hassio.png b/apps/hassio/hassio.png new file mode 100644 index 000000000..8fce958e4 Binary files /dev/null and b/apps/hassio/hassio.png differ diff --git a/apps/hassio/interface.html b/apps/hassio/interface.html new file mode 100644 index 000000000..0b9f4a955 --- /dev/null +++ b/apps/hassio/interface.html @@ -0,0 +1,106 @@ + + + + + + +

Config

+ + + + + + + + + + diff --git a/apps/hassio/metadata.json b/apps/hassio/metadata.json new file mode 100644 index 000000000..0e9c6a2d5 --- /dev/null +++ b/apps/hassio/metadata.json @@ -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"}] +} \ No newline at end of file