diff --git a/apps/calculator/README.md b/apps/calculator/README.md index 62f6cef24..29edd433a 100644 --- a/apps/calculator/README.md +++ b/apps/calculator/README.md @@ -20,9 +20,9 @@ Bangle.js 1 - SELECT: BTN2 Bangle.js 2 -- Swipes to change visible buttons -- Click physical button to exit -- Press upper left corner of screen to exit (where the red back button would be) +- Swipe up or down to go back to the number input +- Swipe to the left for operators, swipe to the right for the special functions +- Exit by pressing the physical button or the upper left corner of screen to exit (where the red back button would be) ## Creator diff --git a/apps/clock_info/ChangeLog b/apps/clock_info/ChangeLog index 870808eff..97e62e238 100644 --- a/apps/clock_info/ChangeLog +++ b/apps/clock_info/ChangeLog @@ -6,3 +6,4 @@ 0.05: Reported image for battery is now transparent (2v18+) 0.06: When >1 clockinfo, swiping one back tries to ensure they don't display the same thing 0.07: Developer tweak: clkinfo load errors are emitted +0.08: Pass options to show(), hide() and run(), and add focus() and blur() item methods diff --git a/apps/clock_info/README.md b/apps/clock_info/README.md index 7e1a3d637..031f89121 100644 --- a/apps/clock_info/README.md +++ b/apps/clock_info/README.md @@ -70,10 +70,12 @@ Note that each item is an object with: } ``` -* `item.show` : called when item should be shown. Enables updates. Call BEFORE 'get' -* `item.hide` : called when item should be hidden. Disables updates. +* `item.show` : called when item should be shown. Enables updates. Call BEFORE 'get'. Passed the clockinfo options (same as what's returned from `addInteractive`). +* `item.hide` : called when item should be hidden. Disables updates. Passed the clockinfo options. * `.on('redraw', ...)` : event that is called when 'get' should be called again (only after 'item.show') * `item.run` : (optional) called if the info screen is tapped - can perform some action. Return true if the caller should feedback the user. +* `item.focus` : called when the item is focussed (the user has tapped on it). Passed the clockinfo options. +* `item.blur` : called when the item is unfocussed (the user has tapped elsewhere, the screen has locked, etc). Passed the clockinfo options. See the bottom of `lib.js` for example usage... diff --git a/apps/clock_info/lib.js b/apps/clock_info/lib.js index e6c9eb27f..d35ac5cf0 100644 --- a/apps/clock_info/lib.js +++ b/apps/clock_info/lib.js @@ -234,7 +234,7 @@ exports.addInteractive = function(menu, options) { options.redrawHandler = ()=>drawItem(itm); itm.on('redraw', options.redrawHandler); itm.uses = (0|itm.uses)+1; - if (itm.uses==1) itm.show(); + if (itm.uses==1) itm.show(options); itm.emit("redraw"); } function menuHideItem(itm) { @@ -242,7 +242,7 @@ exports.addInteractive = function(menu, options) { delete options.redrawHandler; itm.uses--; if (!itm.uses) - itm.hide(); + itm.hide(options); } // handling for swipe between menu items function swipeHandler(lr,ud){ @@ -284,38 +284,47 @@ exports.addInteractive = function(menu, options) { E.stopEventPropagation&&E.stopEventPropagation(); } Bangle.on("swipe",swipeHandler); + const blur = () => { + options.focus=false; + delete Bangle.CLKINFO_FOCUS; + const itm = menu[options.menuA].items[options.menuB]; + let redraw = true; + if (itm.blur && itm.blur(options) === false) + redraw = false; + if (redraw) options.redraw(); + }; + const focus = () => { + let redraw = true; + Bangle.CLKINFO_FOCUS=true; + if (!options.focus) { + options.focus=true; + const itm = menu[options.menuA].items[options.menuB]; + if (itm.focus && itm.focus(options) === false) + redraw = false; + } + if (redraw) options.redraw(); + }; let touchHandler, lockHandler; if (options.x!==undefined && options.y!==undefined && options.w && options.h) { touchHandler = function(_,e) { if (e.x(options.x+options.w) || e.y>(options.y+options.h)) { - if (options.focus) { - options.focus=false; - delete Bangle.CLKINFO_FOCUS; - options.redraw(); - } + if (options.focus) + blur(); return; // outside area } if (!options.focus) { - options.focus=true; // if not focussed, set focus - Bangle.CLKINFO_FOCUS=true; - options.redraw(); + focus(); } else if (menu[options.menuA].items[options.menuB].run) { Bangle.buzz(100, 0.7); - menu[options.menuA].items[options.menuB].run(); // allow tap on an item to run it (eg home assistant) - } else { - options.focus=true; - Bangle.CLKINFO_FOCUS=true; + menu[options.menuA].items[options.menuB].run(options); // allow tap on an item to run it (eg home assistant) } }; Bangle.on("touch",touchHandler); if (settings.defocusOnLock) { lockHandler = function() { - if(options.focus) { - options.focus=false; - delete Bangle.CLKINFO_FOCUS; - options.redraw(); - } + if(options.focus) + blur(); }; Bangle.on("lock", lockHandler); } @@ -352,6 +361,7 @@ exports.addInteractive = function(menu, options) { return true; }; + if (options.focus) focus(); delete settings; // don't keep settings in RAM - save space return options; }; diff --git a/apps/clock_info/metadata.json b/apps/clock_info/metadata.json index 993f112e7..351425a8f 100644 --- a/apps/clock_info/metadata.json +++ b/apps/clock_info/metadata.json @@ -1,7 +1,7 @@ { "id": "clock_info", "name": "Clock Info Module", "shortName": "Clock Info", - "version":"0.07", + "version":"0.08", "description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)", "icon": "app.png", "type": "module", 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 diff --git a/apps/smpltmr/ChangeLog b/apps/smpltmr/ChangeLog index dcc5021b5..2c073ff43 100644 --- a/apps/smpltmr/ChangeLog +++ b/apps/smpltmr/ChangeLog @@ -5,4 +5,5 @@ 0.05: Updated clkinfo icon. 0.06: Ensure Timer supplies an image for clkinfo items 0.07: Update clock_info to avoid a redraw -0.08: Timer ClockInfo now updates once a minute \ No newline at end of file +0.08: Timer ClockInfo now updates once a minute +0.09: Timer ClockInfo resets to timer menu when blurred diff --git a/apps/smpltmr/clkinfo.js b/apps/smpltmr/clkinfo.js index d68372f15..a7a6bf71b 100644 --- a/apps/smpltmr/clkinfo.js +++ b/apps/smpltmr/clkinfo.js @@ -71,13 +71,19 @@ ] }; + const restoreMainItem = function(clkinfo) { + clkinfo.menuB = 0; + // clock info redraws after this + }; + var offsets = [+5,-5]; offsets.forEach((o, i) => { smpltmrItems.items = smpltmrItems.items.concat({ name: null, get: () => ({ text: (o > 0 ? "+" : "") + o + " min.", img: smpltmrItems.img }), show: function() { }, - hide: function () { }, + hide: function() { }, + blur: restoreMainItem, run: function() { if(o > 0) increaseAlarm(o); else decreaseAlarm(Math.abs(o)); diff --git a/apps/smpltmr/metadata.json b/apps/smpltmr/metadata.json index db492b0c1..98affcfe6 100644 --- a/apps/smpltmr/metadata.json +++ b/apps/smpltmr/metadata.json @@ -2,7 +2,7 @@ "id": "smpltmr", "name": "Simple Timer", "shortName": "Simple Timer", - "version": "0.08", + "version": "0.09", "description": "A very simple app to start a timer.", "icon": "app.png", "tags": "tool,alarm,timer,clkinfo", diff --git a/core b/core index bd301be33..0222d3c5a 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit bd301be3324775a8f464328ba9e34f750d503a2b +Subproject commit 0222d3c5ac608a1b842ffc1f1f79e19276d648fe diff --git a/typescript/types/clock_info.d.ts b/typescript/types/clock_info.d.ts index b12732683..7f48cbf0a 100644 --- a/typescript/types/clock_info.d.ts +++ b/typescript/types/clock_info.d.ts @@ -11,10 +11,12 @@ declare module ClockInfo { type MenuItem = { name: string, - show(): void, - hide(): void, + show(options: InteractiveOptions): void, + hide(options: InteractiveOptions): void, on(what: "redraw", cb: () => void): void, // extending from Object - run?(): void, + run?(options: InteractiveOptions): void, + focus?(options: InteractiveOptions): void | false, + blur?(options: InteractiveOptions): void | false, } & ( { hasRange: true, diff --git a/webtools b/webtools index af870d7b8..8d671ad0d 160000 --- a/webtools +++ b/webtools @@ -1 +1 @@ -Subproject commit af870d7b8386bfa824b07b268bce414e4daf3fbb +Subproject commit 8d671ad0dfb1d5a36f4ee9952390f4d79019e61d