Issue newline before GB commands (solves issue with console.log and ignored commands)

pull/2831/head
Gordon Williams 2023-06-19 19:19:32 +01:00
parent 69205d3480
commit ba7a2f7996
27 changed files with 64 additions and 31 deletions

View File

@ -25,4 +25,4 @@
0.24: Handle new 'nav' event for navigation
0.25: Added option to 'ignore' an app from the message
0.26: Change handling of GPS status to depend on GPS events instead of connection events
0.27: Issue newline before GB commands (solves issue with console.log and ignored commands)

View File

@ -2,7 +2,7 @@
"id": "android",
"name": "Android Integration",
"shortName": "Android",
"version": "0.26",
"version": "0.27",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge",

View File

@ -1,8 +1,6 @@
(function(back) {
function gb(j) {
function gbSend(j) {
Bluetooth.println("");
Bluetooth.println(JSON.stringify(j));
}
var settings = require("Storage").readJSON("android.settings.json",1)||{};
@ -16,8 +14,8 @@
/*LANG*/"Find Phone" : () => E.showMenu({
"" : { "title" : /*LANG*/"Find Phone" },
"< Back" : ()=>E.showMenu(mainmenu),
/*LANG*/"On" : _=>gb({t:"findPhone",n:true}),
/*LANG*/"Off" : _=>gb({t:"findPhone",n:false}),
/*LANG*/"On" : _=>gbSend({t:"findPhone",n:true}),
/*LANG*/"Off" : _=>gbSend({t:"findPhone",n:false}),
}),
/*LANG*/"Keep Msgs" : {
value : !!settings.keep,

View File

@ -1,3 +1,4 @@
0.01: First Version
0.02: Remove HID requirement, update screen
0.03: Fix for Bangle 2, toggle find with top half of screen, exit touch bottom half of screen
0.04: Issue newline before GB commands (solves issue with console.log and ignored commands)

View File

@ -5,11 +5,11 @@ var finding = false;
function draw() {
// show message
g.clear(g.theme.bg);
g.clear(g.theme.bg);
g.setColor(g.theme.fg);
g.setFont("Vector", fontSize);
g.setFontAlign(0,0);
if (finding) {
g.drawString("Finding...", g.getWidth()/2, (g.getHeight()/2)-20);
g.drawString("Click to stop", g.getWidth()/2, (g.getHeight()/2)+20);
@ -20,6 +20,7 @@ function draw() {
}
function findPhone(v) {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"findPhone", n:v}));
}
@ -43,7 +44,7 @@ if (process.env.HWVERSION == 1) {
if (process.env.HWVERSION == 2) {
Bangle.on('touch', function(button, xy) {
// click top part of the screen to stop start
if (xy.y < g.getHeight() / 2) {
find();

View File

@ -2,7 +2,7 @@
"id": "findphone",
"name": "Find Phone",
"shortName": "Find Phone",
"version": "0.03",
"version": "0.04",
"description": "Find your phone via Gadgetbridge. Click any button to let your phone ring. 📳 Note: The functionality is available even without this app, just go to Settings, App Settings, Gadgetbridge, Find Phone.",
"icon": "app.png",
"tags": "tool,android",

View File

@ -10,3 +10,4 @@
0.10: Simplify touch events
Remove date+time
0.11: Use default Bangle formatter for booleans
0.12: Issue newline before GB commands (solves issue with console.log and ignored commands)

View File

@ -415,6 +415,7 @@ let tCommand = {};
* @param {string} command - "play"/"pause"/"next"/"previous"/"volumeup"/"volumedown"
*/
function sendCommand(command) {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t: "music", n: command}));
// for control color
if (command in tCommand) {

View File

@ -2,7 +2,7 @@
"id": "gbmusic",
"name": "Gadgetbridge Music Controls",
"shortName": "Music Controls",
"version": "0.11",
"version": "0.12",
"description": "Control the music on your Gadgetbridge-connected phone",
"icon": "icon.png",
"screenshots": [{"url":"screenshot_v1_d.png"},{"url":"screenshot_v1_l.png"},

View File

@ -1 +1,2 @@
0.01: Initial version
0.02: Issue newline before GB commands (solves issue with console.log and ignored commands)

View File

@ -22,14 +22,17 @@ var counter = 0; //stores your counted your twists
var tstate = false; //are you ready to count the twists?
function playx() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"music", n:"play"}));
}
function volup() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"music", n:"volumeup"}));
}
function voldn() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"music", n:"volumedown"}));
}

View File

@ -2,7 +2,7 @@
"id": "gbtwist",
"name": "Gadgetbridge Twist Control",
"shortName": "Twist Control",
"version": "0.01",
"version": "0.02",
"description": "Shake your wrist to control your music app via Gadgetbridge",
"icon": "app.png",
"type": "app",

View File

@ -7,3 +7,4 @@
0.07: Update clock_info to avoid a redraw
0.08: Allow swiping to switch triggers
0.09: Improve web interface, arrows in UI
0.10: Issue newline before GB commands (solves issue with console.log and ignored commands)

View File

@ -64,6 +64,7 @@ exports.sendTrigger = function(triggerName){
while(retries > 0){
try{
// Now lets send the trigger that we sould send.
Bluetooth.println("");
Bluetooth.println(JSON.stringify({
t:"intent",
action:"com.espruino.gadgetbridge.banglejs.HA",

View File

@ -1,7 +1,7 @@
{
"id": "ha",
"name": "Home Assistant",
"version": "0.09",
"version": "0.10",
"description": "Integrates your Bangle.js into Home Assistant.",
"icon": "ha.png",
"type": "app",

View File

@ -13,3 +13,4 @@
0.13: Fix drawPyramid function so pyramids are drawn in correct Y position
0.14: Add jumping frame for characters
0.15: Disable notification buzz during Quiet Mode
0.16: Issue newline before GB commands (solves issue with console.log and ignored commands)

View File

@ -92,6 +92,7 @@ function writeSettings(newSettings) {
}
function phoneOutbound(msg) {
Bluetooth.println("");
Bluetooth.println(JSON.stringify(msg));
}

View File

@ -1,7 +1,7 @@
{
"id": "marioclock",
"name": "Mario Clock",
"version": "0.15",
"version": "0.16",
"description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.",
"icon": "marioclock.png",
"type": "clock",

View File

@ -9,4 +9,5 @@
0.11: Prevent module not found error
0.12: Improve README, option to add functions triggered by status changes or time periods, remove old log (<0.10) conversion
0.13: Prevent to stay in consecutive sleep if not worn, correct trigger calling, add trigger object itself as argument to the fn function
0.14: Add "Delete all logfiles before" to interface.html, display all logfiles in the interface
0.14: Add "Delete all logfiles before" to interface.html, display all logfiles in the interface
0.15: Issue newline before GB commands (solves issue with console.log and ignored commands)

View File

@ -303,6 +303,7 @@ if (sleeplog.conf.enabled) {
// send status to gadgetbridge
var gb_kinds = "unknown,not_worn,activity,light_sleep,deep_sleep";
Bluetooth.println("");
Bluetooth.println(JSON.stringify({
t: "act",
act: gb_kinds.split(",")[data.status],

View File

@ -2,7 +2,7 @@
"id":"sleeplog",
"name":"Sleep Log",
"shortName": "SleepLog",
"version": "0.14",
"version": "0.15",
"description": "Log and view your sleeping habits. This app is using the built in movement calculation.",
"icon": "app.png",
"type": "app",

View File

@ -6,3 +6,4 @@
0.06: Make compatible with Fastload Utils app.
0.07: Remove just the specific listeners to not interfere with Quick Launch
when fastloading.
0.08: Issue newline before GB commands (solves issue with console.log and ignored commands)

View File

@ -62,7 +62,7 @@ let touchHandler = function(_, xy) {
backToMenu = true;
E.showMenu(spotifyMenu);
} else if ((R.x-1<x && x<R.x+len) && (R.y2-len<y && y<R.y2+1)) {
//Wake
//Wake
gadgetbridgeWake();
gadgetbridgeWake();
} else if ((R.x2-len<x && x<R.x2+1) && (R.y-1<y && y<R.y+len)) {
@ -76,7 +76,7 @@ let touchHandler = function(_, xy) {
Bangle.removeListener("swipe", swipeHandler);
E.showMenu(savedMenu);
} else if ((R.x-1<x && x<R.x+len) && (R.y+R.h/2-len/2<y && y<R.y+R.h/2+len/2)) {
//Previous
//Previous
spotifyWidget("PREVIOUS");
} else if ((R.x2-len+1<x && x<R.x2+1) && (R.y+R.h/2-len/2<y && y<R.y+R.h/2+len/2)) {
//Next
@ -156,75 +156,93 @@ let albumSearchTerm = function() { // input album to search for
let searchPlayWOTags = function() {//make a spotify search and play using entered terms
searchString = simpleSearch;
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:searchString}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
};
let searchPlayWTags = function() {//make a spotify search and play using entered terms
searchString = (artist=="" ? "":("artist:\""+artist+"\"")) + ((artist!="" && track!="") ? " ":"") + (track=="" ? "":("track:\""+track+"\"")) + (((artist!="" && album!="") || (track!="" && album!="")) ? " ":"") + (album=="" ? "":(" album:\""+album+"\""));
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:searchString}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
};
let playVreden = function() {//Play the track "Vreden" by Sara Parkman via spotify uri-link
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let playVredenAlternate = function() {//Play the track "Vreden" by Sara Parkman via spotify uri-link
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK"]}));
};
let searchPlayVreden = function() {//Play the track "Vreden" by Sara Parkman via search and play
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'artist:"Sara Parkman" track:"Vreden"'}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
};
let openAlbum = function() {//Play EP "The Blue Room" by Coldplay
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:album:3MVb2CWB36x7VwYo5sZmf2", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK"]}));
};
let searchPlayAlbum = function() {//Play EP "The Blue Room" by Coldplay via search and play
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'album:"The blue room" artist:"Coldplay"', "android.intent.extra.focus":"vnd.android.cursor.item/album"}, flags:["FLAG_ACTIVITY_NEW_TASK"]}));
};
let spotifyWidget = function(action) {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:("com.spotify.mobile.android.ui.widget."+action), package:"com.spotify.music", target:"broadcastreceiver"}));
};
let gadgetbridgeWake = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_CLEAR_TASK", "FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS", "FLAG_ACTIVITY_NO_ANIMATION"], package:"gadgetbridge", class:"nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity"}));
};
let spotifyPlaylistDW = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXcRfaeEbxXIgb:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistDM1 = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E365VyzxE0mxF:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistDM2 = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E38LZHLFnrM61:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistDM3 = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E36RU87qzgBFP:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistDM4 = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E396gGyCXEBFh:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistDM5 = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E37a0Tt6CKJLP:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistDM6 = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E36UIQLQK79od:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistDD = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1EfWFiI7QfIAKq:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};
let spotifyPlaylistRR = function() {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXbs0XkE2V8sMO:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}));
};

View File

@ -1,7 +1,7 @@
{
"id": "spotrem",
"name": "Remote for Spotify",
"version": "0.07",
"version": "0.08",
"description": "Control spotify on your android device.",
"readme": "README.md",
"type": "app",

View File

@ -1 +1,2 @@
0.01: first version
0.02: Issue newline before GB commands (solves issue with console.log and ignored commands)

View File

@ -3,7 +3,7 @@
"shortName":"AGPS Widget",
"icon": "widget.png",
"type": "widget",
"version":"0.01",
"version":"0.02",
"description": "Once installed, this widget allows your Bangle.js 2 to load AGPS data in the background **via Gadgetbridge on an Android phone** so it is always up to date. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
"readme": "README.md",
"tags": "widget,agps,http",

View File

@ -6,15 +6,15 @@ if (!global.WIDGETS) {
(function(){
var warnTime = 24*60*60000; //warn missing data
var nextTime = 12*60*60000; //time between requests
var retryTime = 10*60000; //time between retries
var nextTime = 12*60*60000; //time between requests
var retryTime = 10*60000; //time between retries
const JSON_FILE = "agpsdata.json";
var isRequesting = false;
var lastAGPS = 0;
var nextGet = null;
const WIDGET_ID = "widagps";
const WIDGET_ID = "widagps";
WIDGETS[WIDGET_ID]={
area:"tl",
width:24,
@ -28,7 +28,7 @@ if (!global.WIDGETS) {
}
else {
if (Date.now() - lastAGPS > warnTime) {
g.setColor("#f00");
g.setColor("#f00");
w = 24;
}
}
@ -127,6 +127,7 @@ g.drawImage(atob("FBQBAAAABgAAYAAPAACwABOAATgAI8ACPABD4AQ+AEPgD8EAg/AQP4AD+CD/wj
isRequesting = true;
if (Bluetooth.println) {
console.log("On device");
Bluetooth.println("");
Bluetooth.println(JSON.stringify({t:"http", url:"https://www.espruino.com/agps/casic.base64"}));
}
else {
@ -143,19 +144,19 @@ g.drawImage(atob("FBQBAAAABgAAYAAPAACwABOAATgAI8ACPABD4AQ+AEPgD8EAg/AQP4AD+CD/wj
}, 10000);
Bangle.drawWidgets();
}
var data = require("Storage").readJSON(JSON_FILE);
if (data && data.lastAGPS) {
//lastAGPS = data.lastAGPS;
}
}
nextAGPS(Math.max(0, nextTime - (Date.now() - lastAGPS)));
NRF.on('connect', () => {
if (Date.now() - lastAGPS > warnTime) {
nextAGPS(0);
}
});
var testData = "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";
})()