diff --git a/apps/ha/ChangeLog b/apps/ha/ChangeLog index 1ae0d9dce..e87e70eab 100644 --- a/apps/ha/ChangeLog +++ b/apps/ha/ChangeLog @@ -9,3 +9,4 @@ 0.09: Improve web interface, arrows in UI 0.10: Issue newline before GB commands (solves issue with console.log and ignored commands) 0.11: Minor code improvements +0.12: Add slider functionality diff --git a/apps/ha/README.md b/apps/ha/README.md index b0309b040..53bb64899 100644 --- a/apps/ha/README.md +++ b/apps/ha/README.md @@ -3,11 +3,13 @@ This app integrates your Bangle.js into the Home Assistant. # How to use -Click on the left or right side of the screen to select the triggers that you configured. +Click on the left or right side of the screen to select the triggers that you configured. Swiping left or right works as well. Click in the middle of the screen to send the trigger to Home Assistant via Gadgetbridge. +If the trigger is a value one (has `.value = true` in the config), a slider will be displayed for you to alter the value that's sent along with the trigger. + ![](screenshot.png) diff --git a/apps/ha/ha.app.js b/apps/ha/ha.app.js index 14c6dc3be..3f6e606e7 100644 --- a/apps/ha/ha.app.js +++ b/apps/ha/ha.app.js @@ -5,7 +5,7 @@ var ha = require("ha.lib.js"); var W = g.getWidth(), H = g.getHeight(); var position=0; var triggers = ha.getTriggers(); - +var slider; function draw() { g.reset().clearRect(Bangle.appRect); @@ -17,15 +17,60 @@ function draw() { g.setFontAlign(-1,-1); var icon = trigger.getIcon(); - g.setColor(g.theme.fg).drawImage(icon, 12, H/5-2-5); - g.drawString("Home", icon.width + 20, H/5-5); - g.drawString("Assistant", icon.width + 18, H/5+24-5); + var iconY = H / 5 - 2 - 5; + g.setColor(g.theme.fg).drawImage(icon, 12, iconY); - g.setFontAlign(0,0); - var ypos = H/5*3+23; - g.drawRect(W/2-w/2-8, ypos-h/2-8, W/2+w/2+5, ypos+h/2+5); - g.fillRect(W/2-w/2-6, ypos-h/2-6, W/2+w/2+3, ypos+h/2+3); - g.setColor(g.theme.bg).drawString(trigger.display, W/2, ypos); + if (trigger.value) { + if (!slider) { + const R = Bangle.appRect; + console.log("R", R); + const w = 50; + + slider = require("Slider").create(onSlide, { + initLevel: 0, // TODO: fetch this? + + mode: "map", + steps: 100, + timeout: false, + + width: w, + xStart: R.w / 2 - w / 2, + yStart: R.y, + height: R.h - 24, + + dragRect: { + x: R.w * 0.3, + x2: R.w * 0.6, + y: R.y, + y2: R.h, + }, + }); + Bangle.prependListener('drag', slider.f.dragSlider); + } + + const r = slider.c.borderRect; + g.setColor(g.theme.fg) + .setFontAlign(0, 0) + .drawString("HA", (r.x + r.w + W) / 2, iconY + g.imageMetrics(icon).height / 2) + .setFontAlign(0, 1) + .drawString(trigger.display, W / 2, H); + + slider.f.draw(slider.v.level); + }else{ + if (slider) { + slider.f.remove(); + slider = undefined; + } + + g.drawString("Home", icon.width + 20, H/5-5); + g.drawString("Assistant", icon.width + 18, H/5+24-5); + + g.setFontAlign(0,0); + var ypos = H/5*3+23; + g.drawRect(W/2-w/2-8, ypos-h/2-8, W/2+w/2+5, ypos+h/2+5); + g.fillRect(W/2-w/2-6, ypos-h/2-6, W/2+w/2+3, ypos+h/2+3); + g.setColor(g.theme.bg).drawString(trigger.display, W/2, ypos); + } // draw arrows g.setColor(g.theme.fg); @@ -39,6 +84,26 @@ function draw() { } } +var lastLevel; +var lastTouch; + +function onSlide(mode, level, e) { + lastTouch = Date.now(); + + if (!e) return; + + if (e.b !== 0) { + if (lastLevel == null) + lastLevel = level; + } else { + if (lastLevel != null && lastLevel !== level) { + // we've had a drag and level has changed + ha.sendValue(triggers[position].trigger, level); + lastLevel = null; + } + } +} + function toLeft() { Bangle.buzz(40, 0.6); position -= 1; @@ -63,31 +128,36 @@ function sendTrigger() { }); } -Bangle.on('touch', function(btn, e){ - var left = parseInt(g.getWidth() * 0.3); +Bangle.on('touch', (btn, e) => { + if (Date.now() - lastTouch < 250) return; + lastTouch = Date.now(); + + var left = g.getWidth() * 0.3; var right = g.getWidth() - left; var isLeft = e.x < left; var isRight = e.x > right; if(isLeft){ toLeft(); - } - if(isRight){ + }else if (isRight){ toRight(); - } - if(!isRight && !isLeft){ + }else{ sendTrigger(); } }); Bangle.on("swipe", (lr,ud) => { - if (lr == -1) { - toLeft(); - } - if (lr == 1) { - toRight(); - } - }); + if (slider) return; // "disable" swiping on sliders + if (Date.now() - lastTouch < 250) return; + lastTouch = Date.now(); + + if (lr == -1) { + toLeft(); + } else if (lr == 1) { + toRight(); + } + E.stopEventPropagation && E.stopEventPropagation(); +}); // Send intent that the we started the app. diff --git a/apps/ha/ha.lib.js b/apps/ha/ha.lib.js index 7b26b02b2..89802b28d 100644 --- a/apps/ha/ha.lib.js +++ b/apps/ha/ha.lib.js @@ -2,8 +2,8 @@ * This library can be used to read all triggers that a user * configured and send a trigger to homeassistant. */ -function _getIcon(trigger){ - const icon = trigger.icon; +function _getIcon(){ + const icon = this.icon; if(icon == "light"){ return { width : 48, height : 48, bpp : 1, @@ -33,28 +33,27 @@ function _getIcon(trigger){ } exports.getTriggers = function(){ - var triggers = [ - {display: "Empty", trigger: "NOP", icon: "ha"}, - ]; + var triggers; try{ - triggers = require("Storage").read("ha.trigger.json"); - triggers = JSON.parse(triggers); - - // We lazy load all icons, otherwise, we have to keep - // all the icons n times in memory which can be - // problematic for embedded devices. Therefore, - // we lazy load icons only if needed using the getIcon - // method of each trigger... - triggers.forEach(trigger => { - trigger.getIcon = function(){ - return _getIcon(trigger); - } - }) + triggers = require("Storage").readJSON("ha.trigger.json"); } catch(e) { // In case there are no user triggers yet, we show the default... + console.log("ha: error loading triggers:", e); + triggers = [ + {display: "Empty", trigger: "NOP", icon: "ha"}, + ]; } + // We lazy load all icons, otherwise, we have to keep + // all the icons n times in memory which can be + // problematic for embedded devices. Therefore, + // we lazy load icons only if needed using the getIcon + // method of each trigger... + triggers.forEach(trigger => { + trigger.getIcon = _getIcon.bind(trigger); + }) + return triggers; } @@ -66,16 +65,26 @@ exports.sendTrigger = function(triggerName){ // Now lets send the trigger that we sould send. Bluetooth.println(""); Bluetooth.println(JSON.stringify({ - t:"intent", - action:"com.espruino.gadgetbridge.banglejs.HA", - extra:{ - trigger: triggerName - }}) + t:"intent", + action:"com.espruino.gadgetbridge.banglejs.HA", + extra:{ + trigger: triggerName + }}) ); - retries = -1; + break; } catch(e){ retries--; } } -} \ No newline at end of file +} + +exports.sendValue = function(trigger, value){ + Bluetooth.println( + JSON.stringify({ + t: "intent", + action: "com.espruino.gadgetbridge.banglejs.HA", + extra: { trigger, value }, + }) + ); +}; diff --git a/apps/ha/metadata.json b/apps/ha/metadata.json index 4983aed7c..3ddf9b858 100644 --- a/apps/ha/metadata.json +++ b/apps/ha/metadata.json @@ -1,7 +1,7 @@ { "id": "ha", "name": "Home Assistant", - "version": "0.11", + "version": "0.12", "description": "Integrates your Bangle.js into Home Assistant using Android Integration/Gadgetbridge", "icon": "ha.png", "type": "app", diff --git a/modules/Slider.js b/modules/Slider.js index 33bda1245..49ba56c26 100644 --- a/modules/Slider.js +++ b/modules/Slider.js @@ -125,6 +125,7 @@ exports.create = function(cb, conf) { if (o.v.timeoutID) {clearTimeout(o.v.timeoutID); o.v.timeoutID = undefined;} if (e.b==0 && !o.v.timeoutID && (o.c.timeout || o.c.timeout===0)) o.v.timeoutID = setTimeout(o.f.remove, 1000*o.c.timeout); + let cbObj; if (useMap && o.f.wasOnIndicator(o.v.exFirst)) { // If draging starts on the indicator, adjust one-to-one. let input = !o.c.horizontal? @@ -134,7 +135,7 @@ exports.create = function(cb, conf) { o.v.level = Math.min(Math.max(input,0),o.c.steps); - o.v.cbObj = {mode:"map", value:o.v.level}; + cbObj = {mode:"map", value:o.v.level}; } else if (useIncr) { // Heavily inspired by "updown" mode of setUI. @@ -149,16 +150,16 @@ exports.create = function(cb, conf) { o.v.level = Math.min(Math.max(o.v.level-incr,0),o.c.steps); - o.v.cbObj = {mode:"incr", value:incr}; + cbObj = {mode:"incr", value:incr}; } } - if (o.v.cbObj && (o.v.level!==o.v.prevLevel||o.v.level===0||o.v.level===o.c.steps)) { - cb(o.v.cbObj.mode, o.v.cbObj.value); + if (cbObj && (o.v.level!==o.v.prevLevel||o.v.level===0||o.v.level===o.c.steps||e.b===0)) { + cb(cbObj.mode, cbObj.value, e); o.f.draw&&o.f.draw(o.v.level); } - o.v.cbObj = null; o.v.prevLevel = o.v.level; o.v.ebLast = e.b; + if (e.b==0) o.v.dragActive = false; } }; diff --git a/modules/Slider.md b/modules/Slider.md index eb2291d25..8e3571b5e 100644 --- a/modules/Slider.md +++ b/modules/Slider.md @@ -18,12 +18,16 @@ Bangle.on("drag", slider.f.dragSlider); // Bangle.prependListener("drag", slider.f.dragSlider); ``` -`callbackFunction` (`cb`) (first argument) determines what `slider` is used for. `slider` will pass two arguments, `mode` and `feedback` (`fb`), into `callbackFunction` (if `slider` is interactive or auto progressing). The different `mode`/`feedback` combinations to expect are: +`callbackFunction` (`cb`) (first argument) determines what `slider` is used for. `slider` will pass three arguments, `mode`, `feedback` (`fb`) and (if fired from an input event) `event` (`e`), into `callbackFunction` (if `slider` is interactive or auto progressing). The different `mode`/`feedback` combinations to expect are: - `"map", o.v.level` | current level when interacting by mapping interface. - `"incr", incr` | where `incr` == +/-1, when interacting by incrementing interface. - `"remove", o.v.level` | last level when the slider times out. - `"auto", o.v.level` | when auto progressing. +The event will be a drag, from the `Bangle.on('drag', ...)` event. + +The callback function will always be called for the "final" event, which is when the user lifts their finger from the screen. This can be detected by looking for `e.b == 0`. + `configObject` (`conf`) (second argument, optional) has the following defaults: ```js @@ -91,7 +95,7 @@ slider = require("Slider").create(()=>{}, {autoProgress:true}) r: 22 } } } -> +> ``` Tips ----