Merge pull request #3375 from bobrippling/feat/ha
ha: add value triggers (using the Slider lib)master
commit
86680eb1d3
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||

|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.sendValue = function(trigger, value){
|
||||
Bluetooth.println(
|
||||
JSON.stringify({
|
||||
t: "intent",
|
||||
action: "com.espruino.gadgetbridge.banglejs.HA",
|
||||
extra: { trigger, value },
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
----
|
||||
|
|
|
|||
Loading…
Reference in New Issue